Search code examples
reactjsreact-routergraphqlrelayjs

Navigating relay.js app with RelayRouter throwing Invalid State Change error


I'm writing a relay.js app, and I'm using react-relay-router for routing. Routing works, but I believe it is exposing a misunderstanding on my part of how to organize query fragments across different views.

routes defined in app.js

const rootComponent =
  (<RelayRouter history={hashHistory}>
    <Route path="/"
      component={App}
      queries={ViewerQueries}
      onReadyStateChange={this.handleStateChange}
    >
      <IndexRoute
        component={Dashboard}
        queries={ViewerQueries}
      />
      <Route
        path="/org_setup"
        component={OrgSetup}
        queries={ViewerQueries}
      />
      <Route
        path="/admin/data_models"
        component={DataModelList}
        queries={ViewerQueries}
      />
    </Route>
  </RelayRouter>);
ReactDOM.render(rootComponent, mountNode);

query fragment declaration in DataModelList.js

export default Relay.createContainer(DataModelList, {
  fragments: {
    viewer: () => Relay.QL`
      fragment on User {
        dataModels(first: 10) {
          edges {
            node {
              id,
              name,
              isAuthority,
              categories(first: 10) {
                edges {
                  node {
                    id,
                    name,
                    tags(first: 10) {
                      edges {
                        node {
                          id,
                          code,
                          label,
                        }
                      }
                    }
                  }
                }
              },
              adapter {
                categories(first: 10) {
                  edges {
                    node {
                      id,
                      name,
                    }
                  }
                }
              }
            }
          }
        },
      }
    `,
  },
});

query fragment in OrgSetup.js

export default Relay.createContainer(OrgSetup, {
  fragments: {
    viewer: () => Relay.QL`
      fragment on User {
        dataModels(first: 10) {
          edges {
            node {
              id,
              name,
              isAuthority,
              categories(first: 10) {
                edges {
                  node {
                    id,
                    name,
                    tags(first: 10) {
                      edges {
                        node {
                          id,
                          code,
                          label,
                        }
                      }
                    }
                  }
                }
              },
            }
          }
        },
      }
    `,
  },
});

When navigating between DataModelList and OrgSetup (in either direction) I see the following error in the console:

Server request for query `ViewerQueries` failed for the following reasons:

1. Cannot query field "node" on "Query".
   rQueries($id_0:ID!){node(id:$id_0){id,__typename,...F0}} fra

Warning: RelayReadyState: Invalid state change from 
{"aborted":false,"done":false,"error":{"source":{"data":null,"errors":
[{"message":"Cannot query field \"node\" on \"Query\".","locations":
[{"line":1,"column":32}]}]}},"ready":false,"stale":false}` to `{"error":
{"source":{"data":null,"errors":[{"message":"Cannot query field \"node\" on 
\"Query\".","locations":[{"line":1,"column":32}]}]}}}`.warning @ 
app.js:31567update @ app.js:39409onRejected @ app.js:39035tryCallOne @ 
app.js:30045(anonymous function) @ app.js:30131flush @ app.js:30279

I'm able to prevent this error from occurring by making the query fragments in the two views identical - specifically by adding the adapter fragment.

However, this feels wrong. They don't need to be identical (in fact they are somewhat intentionally bloated at the moment since this is a learning app for me), and other views will of course not share these data needs.

  1. What is the best practice for keeping query declarations concise? Ideally I would have top level views only declare top level data requirements. Concretely, in my example view files I would request only the viewer and their dataModels, and then allow child components to express their needs, requesting any nested data (eg categories, tags, or adapters and their respective categories).
  2. What does the invalid state change error really mean? I can see that it's satisfied by the queries matching exactly, but I can't imagine unrelated views are required to share query declarations. I suspect this is a byproduct of my doing something wrong elsewhere.

Thanks!


Solution

  • When the query changes (eg. due to a route transition, or a call to setVariables()) Relay will diff the query you're coming from and the query you're headed to, computing the minimal query to get you from A to B. In your case, Relay starts from the series of known records stored on the client (the already-fetched ones) and tries to fetch only the new fields on those exact records in an effort to build a complete picture of the world. These refetches rely on something called the node interface.

    Relay-compliant GraphQL servers must expose a root field called node that can fetch any record given a global ID. This field must take an id argument of type GraphQLID!.

    For an example of how to build such a root field, see this graphql-js example.