Giter Site home page Giter Site logo

facebook / relay Goto Github PK

View Code? Open in Web Editor NEW
18.2K 331.0 1.8K 5.83 GB

Relay is a JavaScript framework for building data-driven React applications.

Home Page: https://relay.dev

License: MIT License

JavaScript 43.09% Shell 0.02% CSS 0.53% HTML 1.26% Rust 54.16% Python 0.02% TypeScript 0.34% MDX 0.57%

relay's Introduction

Relay · GitHub license npm version

Relay is a JavaScript framework for building data-driven React applications.

  • Declarative: Never again communicate with your data store using an imperative API. Simply declare your data requirements using GraphQL and let Relay figure out how and when to fetch your data.
  • Colocation: Queries live next to the views that rely on them, so you can easily reason about your app. Relay aggregates queries into efficient network requests to fetch only what you need.
  • Mutations: Relay lets you mutate data on the client and server using GraphQL mutations, and offers automatic data consistency, optimistic updates, and error handling.

See how to use Relay in your own project.

Example

The relay-examples repository contains an implementation of TodoMVC. To try it out:

git clone https://github.com/relayjs/relay-examples.git
cd relay-examples/todo
yarn
yarn build
yarn start

Then, just point your browser at http://localhost:3000.

Contribute

We actively welcome pull requests, learn how to contribute.

Users

We have a community-maintained list of people and projects using Relay in production.

License

Relay is MIT licensed.

Thanks

We'd like to thank the contributors that helped make Relay in open source possible.

The open source project relay-hooks allowed the community to experiment with Relay and React Hooks, and was a source of valuable feedback for us. The idea for the useSubscription hook originated in an issue on that repo. Thank you @morrys for driving this project and for playing such an important role in our open source community.

Thank you for helping make this possible!

relay's People

Contributors

alunyov avatar captbaritone avatar cpojer avatar davidmccabe avatar dependabot[bot] avatar devknoll avatar evanyeung avatar gabelevi avatar gkz avatar josephsavona avatar kassens avatar leebyron avatar mjmahone avatar mofeiz avatar monicatang avatar mroch avatar mvsampath avatar pieterv avatar poteto avatar rbalicki2 avatar rubennorte avatar samchou19815 avatar steveluscher avatar tbezman avatar tyao1 avatar voideanvalue avatar wincent avatar yungsters avatar yuzhi avatar zertosh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

relay's Issues

Passing variables to containers from route/query root

Let's say I have a client side route like /widgets?color={color}. Correspondingly, I want a query that looks like

query {
  viewer {
    widgets(color: $color first: 10) {
      ...widgetFields
    }
  }
}

After some stumbling, it seems like the best way to do this is something like:

WidgetListView = Relay.createContainer(WidgetListView, {
  initialVariables: {
    color: null
  },

  fragments: {
    viewer: () => Relay.QL`
      widgets(color: $color first: 10) {
        ${WidgetList.getFragment('widgets')}
      }
    `
  }
});

class WidgetListRoute extends Relay.Route {
  static routeName = 'WidgetListRoute';

  static routeParams = {
    color: {}
  };

  static queries = {
    viewer: (Component, {color}) => Relay.QL`
      query {
        viewer {
          ${Component.getFragment('viewer', {color})}
        }
      }
    `
  }
}

class WidgetListApp extends React.Component {
  render() {
    return (
      <Relay.RootContainer
        Component={WidgetListView}
        route={new WidgetListRoute({color: this.props.color})}
      />
    );
  }
}

Is this indeed the correct approach, or am I horribly abusing undocumented features?

Why there's no "NODE_ADD" type?

Since there are NODE_DELETE and RANGE_DELETE types, I'm wondering why NODE_ADD is not needed?

Personally I'm guessing that's because when you create a new object, it should always belong to one of the edges, hence RANGE_ADD is good enough, is that correct?

Q: beforeSend hooks in DefaultNetworkLayer

Hey, so I'm playing around with Relay and I need to add an authentication token to all outgoing HTTP calls made by Relay. Is my only option to implement my own NetworkLayer?

Do you think it'd be useful to add a beforeSend type hook inside the layer so we can make changes to the requests before sending them off? In my case, I need to add a HTTP header.

Let me know if there's another place where I should be asking this question! Thanks!

"Relay only supports root fields with zero or one argument"

Totally understand this error for mutations (given the standard INPUT_TYPE), but I just got this on a root query in a route. Is this intended to apply to all root queries, or just mutations?

In the mean time, I'll just refactor that query to take a single input, like mutations.

Non-string cursors

We'd like to use a custom scalar type for cursors, but a spec says that it should be a String. I wonder if spec can be changed so that any scalar can be used as cursor.

query parameters changing input types when converted to node queries

Not certain if this is a bug, but...

Modifying the TodoApp container in the todo example like this (ignoring the ...on User part):

export default Relay.createContainer(TodoApp, {
  initialVariables: {
    cursor: null
  },
  fragments: {
    viewer: () => Relay.QL`
      fragment on User {
        ...on User {
          todos(first: 4, after: $cursor) {
            edges {
              cursor,
              node {
                id,
              },
            },
            totalCount,
            ${TodoList.getFragment('todos')},
            ${TodoListFooter.getFragment('todos')},
          }
        }
        ${AddTodoMutation.getFragment('viewer')},
        ${TodoList.getFragment('viewer')},
        ${TodoListFooter.getFragment('viewer')},
      }
    `,
  },
});

results in a node query with a subcomponent like this (when the cursor is change):

..._todosdf82087a:todos(after:\"YXJyYXljb25uZWN0aW9uOjM=\",first:\"4\")...

resulting in the error:

{
  "errors": [
    {
      "message": "Argument \"first\" expected type \"Int\" but got: \"\"4\"\".",
      "locations": [
        {
          "line": 1,
          "column": 665
        }
      ]
    }
  ]
}

Make dist/relay[.min].js CommonJS compatible

Would be great if the Relay dist files could be CommonJS compatible (in the same way the react[.min].js files are with the whole ...typeof exports... surrounding block)

Among other things, this would make it easier to use Relay with browserify.

With dist/relay[.min].js built on the latest master I get errors like Uncaught TypeError: Cannot read property 'MutationObserver' of undefined

`hasOptimisticUpdate` returns `true` after mutation is applied

hasOptimisticUpdate returns true after mutation is applied if optimistic update data equals to real one. easy to get this behaviour in provided relay-treasurehunt example by adding hasTreasure: false to hidingSpot in CheckHidingSpotForTreasureMutation.getOptimisticResponse

Better workflow for examples & relay-starter-kit

Might be worth coming up with a better workflow to manage these projects. It's great that they can all run out of the box with their own npm install, but so far any change to one of them means a change to all four.

We should see if we can come up with a script to generate the shared bits (similar to react/addons in 0.14.0). We could keep the output in git to keep it easy to use (if that's a goal) or just add a top level task to generate them.

😉

example doest not work on safari

git clone https://github.com/facebook/relay.git
cd relay/examples/todo && npm install
npm start

when access http://localhost:3000 using firefox ,everything is ok,but safari show one error

[Error] TypeError: undefined is not a function (evaluating ''\\ '.repeat(prefix.length)')
    (匿名函数) (relay.js, line 2966)

DefaultNetworkLayer isn't exported

Relay.DefaultNetworkLayer isn't being exported, presumably because package.json has main set to lib/RelayPublic.js instead of index.js

Document says "renderLoading callback is always called with a data argument"

In the section that introduces the renderFetched callback in root container guide, it reads,

This snippet configures Relay.RootContainer to render ProfilePicture within a ScrollView
component as soon as data is ready.

The renderLoading callback is always called with a data argument, which is a an object
mapping from propName to query data. It is expected that the renderLoading callback
renders the supplied Component with them (e.g. using the JSX spread attributes feature).

Note

Even though we have access to the data object in renderLoading, the actual data is
intentionally opaque. This prevents the renderLoading from creating an implicit dependency
on the fragments declared by Component.

I suppose all renderLoading mentioned above should be renderFetched instead?

Q: Server rendering?

It seems to me that this would require an API to serialize the store state and rehydrate on the client otherwise the initial client render may differ from the server rendered one.

Is this feature planned?

Thanks

Make Relay work with React Native out of the box

The remaining steps are:

  • Relay/React Native/fbjs versioning
  • Use the appropriate unstableBatchedUpdates depending on React/React Native
  • Version of fetch polyfill for React Native
  • Document the use of babel-relay-plugin with React Native (see discussion at #714 (comment))
  • Create a fresh React Native project, set up Relay, configure the plugin per the documentation added in the previous step, and make sure everything works as expected.

Note: edited by @josephsavona

Q: Polymorphic composition

I've got a hierarchy like:

<App>
  <Header/>
  < ... />
</App>

where < ... /> is filled in with a component by the router, depending on which route the user is visiting. Both <Header/> and < ... /> are Relay.Containers.

As far as I can tell, there is no way to make <App> a Relay.Container that includes fragments from <Header/> and < ... />, so I've resorted to sticking both into their own separate RootContainers, with their own routes.

Am I missing something? How does/would FB design this?

Thanks 😄

DefaultNetworkLayer not exposed

Loving this so far, great work.

Can't get DefaultNetworkLayer:

import Relay from 'react-relay';
Relay.DefaultNetworkLayer is undefined

404 in the todo example

When I start the todo example (as per the README) is page is trying to communicate with the GraphQL server on port 3000, but I get a 404 because the GraphQL server is listening on port 8080.

Support type-unique IDs

When implementing a Relay-compatible GraphQL API within an existing application, one point of friction is the requirement of globally-unique IDs as part of the Node interface. Combining the type name with the existing type-unique ID is often suggested, but this can make interop with legacy APIs more involved.

Fundamentally there isn't a reason why the client needs anything other than a type-unique ID, since it always knows the type of the Node it's requesting.

The most obvious implementation to me would be to add a type argument to the node root call. If enabled, Relay would then prepend the type name to any internal DataIDs to keep the simplicity of a single store, but expose type-unique IDs externally to the application.

@leebyron thanks for suggesting I open this issue.

Invalid cursors

@dschafer mentioned that using cursors when, eg, ordering changes is not guaranteed to be correct. Should compliant server implementation:

a) signal error
b) return null
c) proceed as normal, even if result might be pretty random?

Q: Error: Unexpected arg kind: ObjectValue

I tried to pass an input object as an argument to a field, which resulted in following error: Unexpected arg kind: ObjectValue.

The fragment in question looks like this:

fragment on Movie {
  id,
  credits(orderBy: {field: "name", order: ASC}) {
    edges {
      node {
        name
      }
    }
  }
}

The error is thrown here, in GraphQLPrinter.js. It seems only builtin scalar types and variables are supported by GraphQLPrinter. Does this mean the only way to pass an input object in Relay is to write credits(orderBy: $orderBy) and pass it in the variables instead?

Q: Defining data requirement in Relay.Route?

My understanding is that data requirements on Relay Containers are defined statically and are fetched regardless if component is shown.

Therefore logic what data will be actually necessary has to be deduced in Relay.Route from the parameters - that might be coming from url parameters or some other context.

So I guess creating several Relay.Route objects covering different scenarios is way to go currently. I know that its obvious to use multiple Relay.Route when different views are shown. But I mean for example scenario when user is on page with list of albums and clicks on one of them to see pop-up menu with songs list (which is kind of sub-route scenario). That could affect url (using react-router) that could switch to different Relay.Route object that would additionally call getFragment on that pop-up menu.

I noticed in examples that Relay.Route always just call getFragment on Component which is argument. But I assume that calling getFragment directly in Relay.Route also from other components is also reasonable?

Does this approach make sense? Wondering if there are some other tricks how to conditionally change data requirement - and I mean decide whether data be fetched or not (for example based on the fact if pop-up menu is opened or not).

Thanks!

TypeError: undefined is not a function (evaluating ''\\ '.repeat(prefix.length)')

The TodoMVC example from the README does not work in browsers that do not support String#repeat. At time of writing the browsers that support it are Chrome and Firefox. This should either be done manually or the babel polyfill core-js should be included in the build that bundles Relay.

Steps to Reproduce

  1. Follow the directions in the README for running an example.
  2. Load http://localhost:3000/ in a browser without support for String#repeat, i.e. Safari.

Expected

The page should load the TodoMVC app.

Actual

A runtime error occurs before the page can be properly loaded (TypeError: undefined is not a function (evaluating ''\\ '.repeat(prefix.length)')).

import from "react-relay" and webpack bundle doesn't work

Hi,
I was trying to include Relay in my webpack bundle but when I render it throw a "setState on unmounted component" warning and crash.
If I include React and Relay from a script tag everything works fine. Can you tell me why?

The Relay object accessed by import have not the DefaultNetworkLayer property too.
I have to require and inject it by myself.

Have a great day!
Davide

Where to put a more flux-like store variable

I'm currently working on an example from flux-chat to relay
https://github.com/transedward/relay/tree/master/examples/chat
I tried remove everything from flux, but encounter some problem
For example: How to deal with currentID of ThreadStore in Flux when user clicking thread
Since it's not something you would like to put in database but a necessary state for application
I come up with two solution:

  1. put in connectionField like todoApp completeCount,
    https://github.com/facebook/relay/blob/master/examples/todo/data/schema.js#L88
    but since resolve function can only get connection back, I don't where I can send thread.id into function and get access to it?
  2. like the bottom part of this, http://facebook.github.io/relay/docs/guides-routes.html#content
    addEventListener for pushState, and every time you click thread, then window.pushState({currentID}, ''), but stored in a variable in Relay.createContainer
    like I do it here, https://github.com/transedward/relay/blob/master/examples/chat/js/components/MessageSection.js#L62
    But seems like Relay will parse this before any component mounted, you can't access any history state here
    I prefer this approach because in the future I can do thing like /thread/{id}
    then parse the id for currentID but by default when / then display the newest thread

I would to hear some feedback and suggest on this, thanks

QuickStart Tutorial should be iterative

It would be nice if the QuickStart Tutorial followed more of a "Hello, World"/thin-thread model. After the npm install, use as little code as possible to get something showing on the screen. Then, pick a simple feature to add to the "game", and follow that feature all the way from front-end to back-end. The later Guides go through Relay's features one by one, so the tutorial can be organized more iteratively.

GraphQL end of line issue on OSX

In the todo example I'm getting this from the Graph page.

{
  "errors": [
    {
      "message": "Syntax Error GraphQL request (1:1) Unexpected EOF\n\n1: \n   ^\n",
      "locations": [
        {
          "line": 1,
          "column": 1
        }
      ]
    }
  ]
}

Connections Spec hasNextPage & hasPreviousPage inconsistent with implementation

The Connections Specification states that hasNextPage of PageInfo:

hasNextPage will be true if the client is paginating with first, and the server has determined that the client has reached the end of the set of edges defined by their cursors. More formally:

HasNextPage(allEdges, before, after, first, last)

  1. If first was not set, return false.
  2. Let edges be the result of calling ApplyCursorsToEdges(allEdges, before, after).
  3. If edges contains more than first elements, return false.
  4. Return true.

(emphasis mine)

This description seems backwards, and does not reflect the implementation:

  return {
    edges: edges,
    pageInfo: {
      startCursor: firstEdge.cursor,
      endCursor: lastEdge.cursor,
      hasPreviousPage: (firstEdge.cursor !== firstPresliceCursor),
      hasNextPage: (lastEdge.cursor !== lastPresliceCursor)
    }
  };

Instead, it should read:

hasNextPage will be true if the client is paginating with first, and the server has determined that the client has not reached the end of the set of edges defined by their cursors. More formally:

  1. ...
  2. ...
  3. If edges contains more than first elements, return true.
  4. Return false

The same is true for hasPreviousPage.

Websockets and Relay

What are the current thoughts on using Websockets in the network layer?

How would you keep all clients up-to-date when a mutation is sent from a single client? In the other clients' environment, it would just receive a query response but, there wouldn't be a request object to go with it in the network layer. Or is it as simple as Relay is not made with this kind of data flow in mind?

Route returns first array element instead of whole array

this.props.users is object of first user instead of array. Note that top level GraphQL query users has the right type new GraphQLList(userType). Consider this code:

app.jsx:

import Users from './Users'
import HomeRoute from './HomeRoute'

React.render(<Relay.RootContainer Component={Users} route={new HomeRoute()} />, document.getElementById('app'))

Users.jsx:

class Users extends React.Component {
  render() {
    return (
      <div>
        // Error: undefined (.map) is not a function
        // this.props.users is first user object, should be array
        {this.props.users.map(user => {
          return <div>{user.name}</div>
        })}
      </div>
    )
  }
}

export default Relay.createContainer(Users, {
  fragments: {
    users: () => Relay.QL`
      fragment on User {
        name
      }
    `
  }
})

HomeRoute.js:

export default class extends Relay.Route {
  static routeName = 'HomeRoute'
  static path = '/'
  static queries = {
    users: (Component) => Relay.QL`
      query {
        users {
          ${Component.getFragment('users')}
        }
      }
    `,
  }
}

Where could be the problem? Thank you.

"Unexpected invocation at runtime" error.

I'm getting this error:

Error: Invariant Violation: RelayQL: Unexpected invocation at runtime. Either the Babel transform was not set up, or it failed to identify this call site. Make sure it is being used verbatim as `Relay.QL`.

Don't know where is the problem. Error is probably caused by Relay.createContainer function. I'm using Babel in my application, but I'm compiling it via Webpack before running.

Multiple routes examples

Not an issue, just some questions to understand how Routes in Relay work. Example which uses creates routes would be nice to have.

Why do we need 'path' property?
For example, from documentation it looks like developer must handle location changes manually:

window.addEventListener('popstate', () => {
  var userID = getQueryParamFromURI('userID', document.location.href);
  var profileRoute = new ProfileRoute(userID);
  React.render(
    <Relay.RootContainer
      Component={Profile}
      route={profileRoute}
    />
  );
});

not clear what kind of role 'path' property have or will have in future.

[Question/Example] How to search within connections

First off, thank you for your wonderful work. Been looking forward to this day for months :)

I've been tangling around with the examples and starter-kit. Besides encountering issues upon paging (tickets already open), I stumbled upon a feature I wanted to test, but honestly have no idea how to approach best.

Let's take the starter-kit example. I want to introduce a search field to get widgets (of my user) by name. Let's say I want to get all widgets starting with "A", either paginated or not.

How would I need to extend the example to make this happen. I would assume that I need to pass some kind of additional "search" or "find" argument to the widgets sub-query, so I could pass it into the resolve function before returning the arrayed connections.
But currently I do not see how I could add an additional argument to the connection type, to let it go through.

Or would that even be the wrong way to do it and I should make a separate (root) query for this kind of behavior?

An extended example on how to solve this would be great.
Thanks in advance and kind regards,
Daniel

Hydrate scalar fields on client

E.g. a DateTime field could be automatically turned into a Moment.js object.

Is this possible already and I just missed it in the docs?

Document the relationship betwee outputFields, the fat query, and the tracked query in the mutations guide

I believe I have read all the available resource, but still having difficulties to understand why I should explicitly handle the adding mutation on the client via getConfigs, its from example for IntroduceShipMutation mutation.

  getConfigs() {
    return [{
      type: 'RANGE_ADD',
      parentName: 'faction',
      parentID: this.props.faction.id,
      connectionName: 'ships',
      edgeName: 'newShipEdge',
      rangeBehaviors: {
        // When the ships connection is not under the influence
        // of any call, append the ship to the end of the connection
        '': 'append',
        // Prepend the ship, wherever the connection is sorted by age
        'orderby:newest': 'prepend',
      },
    }];
  }

If I look into server side implementation for this mutation, it returns all ships for that faction, so why client should handle this (append) manually?

  outputFields: {
    ship: {
      type: shipType,
      resolve: (payload) => data['Ship'][payload.shipId]
    },
    faction: {
      type: factionType,
      resolve: (payload) => data['Faction'][payload.factionId]
    }

I can see benefit of doing this when server is sending just new ship and not the whole faction. On the other hand if client has to handle these kind of mutation itself it seems like duplicating the logic and having limited options ('append', 'prepend').

And how it all would go together with arguments for connections. Having two ship connections with argument that would filter the result - how to update when new ship is added? This new ship might appear in any of these two connections depending on filtering arguments. How would I make connections to refetch.

Thank you! And apologize in case I missed some relevant docs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.