javascriptreactjsreact-routerrelayjsreact-router-relay

Multiple react-router-relay routes using same Relay root query with different fragment fields


I am running into an issue where when requesting data within two adjacent react-router-relay route components using the same root query, the graphql server query in the second route component invalidates the data previously retrieved from the first.

As an example:

I am using a workaround to attach fields to a 'viewer' root node, in order to query for arrays of data from a root field:

My schema.js includes the following definitions for the viewer (User) type and the root:

var GraphQLUser = new GraphQLObjectType({
   name: 'User',
   isTypeOf: function(obj) { return obj instanceof User },
   fields: function() {
      return {
         id: GraphQLRelay.globalIdField('User'),
         //Fields to expose to app, e.g. cars
         cars: {
            type: GraphQLRelay.connectionDefinitions({
               name: 'Car', nodeType: carType
            }).connectionType,
            args: GraphQLRelay.connectionArgs,
            resolve: function(user, args) {
               return
                 GraphQLRelay.connectionFromPromisedArray(getCars(), args)
            },
        },
     }
 },
 interfaces: [nodeDefinitions.nodeInterface],
});

And the root:

var Root = new GraphQLObjectType({
   name: 'Root',
   fields: {
      viewer: {
         type: GraphQLUser,
         resolve: (root, {} ) => getUser()
    },
    node: nodeDefinitions.nodeField
});

Now, in the main.jsx application entry point, I am defining the following routes:

ReactDOM.render(
   <Router createElement={ReactRouterRelay.createElement}>
      <Route path="/" component={App} queries={ViewerQueries} />
      <Route path="/cars" component={AllCars} queries={ViewerQueries} />
  </Router>,
  document.getElementById('ReactContainer'));

with ViewerQueries.js containing:

export default {
   viewer: () => Relay.QL`query { viewer }`,
};

Now, if the App component includes the following graphql fragment:

exports.Component = Relay.createContainer(App, {
   initialVariables: {
      limit: 10
   },
   fragments: {
      viewer: () => Relay.QL`
         fragment on User {
         cars (first: $limit) {
            edges {
              node {
                 name,
              }
            }
          }
       }`,
   },
 })

And the AllCars component includes the following:

exports.Component = Relay.createContainer(AllCars, {
   initialVariables: {limit: 10},
   fragments: {
      viewer: () => Relay.QL`
         fragment on User {
            cars (first: $limit) {
               edges {
                  node {
                     name,
                     type
                  }
               }
            }
        }`,
     },
  })

what happens is that on rendering of the App component on path '/', the array of cars is returned without problems. However, once the '/cars' path is rendered, the resulting array on `this.props.viewer.cars' is empty. In addition, returning to the path '/' to cause a re-rendering of the App component now also returns an empty array.

On the back-end, it appears the AllCars component causes an additional POST /graphql 200 277.369 ms - 94 trip to the server to fetch the new type specified in the fragment. If the AllCars component only includes the 'name' field, Relay correctly doesn't make another server request, but instead populates the array with data (supposedly cached from the initial App component server request).

Shouldn't it be possible to get the array of cars from either component, with Relay getting any additional data fields specified in the fragments regardless of the order they appear in the routes?

Any help would be greatly appreciated!


Solution

  • The issue ended up being invalid nodedefinitions for the (example) Car type in the schema.js file. The node definitions that I ended up using were:

     var nodeDefinitions = GraphQLRelay.nodeDefinitions(function(globalId) {
       var idInfo = GraphQLRelay.fromGlobalId(globalId)
         if (idInfo.type == 'Car') {
            return getCarById(idInfo.id)
         } else if ...
           // Other types here
         }
        return null
     });
    

    The returned getCarById function didn't correctly resolve the Car object, and the GraphQL server was unable to get the additional data-points requested by Relay, causing a null value to be returned instead, effectively nullifying the previously fetched Car items.

    In addition, the Root definition was invalid, and should be:

    var Root = new GraphQLObjectType({
       name: 'Root',
       fields: {
          viewer: {
             type: GraphQLUser,
             resolve: (root, {} ) => getUser()
          },
          node: nodeDefinitions.nodeField
       }
    });
    

    The misplaced node field definition caused the GraphQL server to complain about not being able to query the 'node' field on the Root query.

    After these corrections, Relay is able to query the same Root field using different datapoints, regardless of placement in the <Route/> tree.

    Although nested routes are probably preferred for master/detail lists of the same object type, at times it is desired to include the same data in non-related React components (e.g. sidebars, headers, etc.)