Search code examples
relayjsrelayrelaymodern

Load data from relay store while the query is loading


I have a common Header and Footer that displays data that it may be already in the relay store and the main PageComponent that expects data from the “query”, but I don’t want to display a full loading screen to the user, I want to display the Header, Footer and the loading screen or content in the middle.

So, as you know, if I wrap everything inside a QueryRenderer, I will have two options: either the query is “loading” or the data is available:

<QueryRenderer
  {...usualProps}
  render={({ error, props }) => {
    <div className="app-wrapper">
      { props || error ? <PageComponent {...props} error={error} /> : <div>Loading...</div>}
    </div>
  }}
/>

Let’s say I have a way to retrieve data from the store manually (as we don’t have something like partial rendering from the store) and I can pass that information to the common Header and Footer while the main query is loading:

<QueryRenderer
  {...usualProps}
  render={({ error, props }) => {
    <div className="app-wrapper">
      <Header {...storeProps} {...props} />
      { props || error ? <PageComponent {...props} error={error} /> : <div>Loading...</div>}
      <Footer {...storeProps} {...props} />
    </div>
  }}
/>

I found that since I have the environment, I can do something like this to get the information from the store:

const storeProps = relayEnvironment.getStore().lookup({
  dataID: 'client:root',
  node: query().fragment, // special query with only the information I need for the `Header`
  variables: {}
});

But then I have a few problems:

  • QueryRenderer disposes the data when is unmounted :cry: (although the information I read from it should be retrieved by the Query too, reading from the store is more like a fallback while the query returns)
  • The syntax looks hacky
  • I have to create a query and compile it (I can live with this anyway)

Probably I’m aproaching the problem in the wrong way and maybe someone did something similar and can throw me a few pointers. (In case nothing works my plan B is to use getDerivedStateFromProp to send query information to custom context and save it there to avoid the store GC problem, for example).

TL:DR: I want to load data from the store while the query finishes loading and then use the data returned from the query.

Any ideas? Let me know if something is not clear


Solution

  • Relay has Observables and Cache that can be used in combination to achieve this.

    You can import { QueryResponseCache } from 'relay-runtime';

    then use it like:

    const cache = new QueryResponseCache({
          size: CACHE_SIZE, // Number of responses to cache
          ttl: CACHE_DURATION, // duration in ms to keep the cache
        })
    

    Now when you get a response from the server, you can store it like

    cache.set(queryId, variables, payload)
    

    And read like:

    cache.get(queryId, variables)
    

    Now for the observables, import { Observable } from 'relay-runtime';

    Then your use it in your network fetch like this:

    const fetchQuery = (operation, variables) => {
        return Observable.create(observer => {
          if (operation.operationKind !== 'mutation') {
            observer.next(cachedData) // cacheData fetched from cache
          }
          const jsonResponse = fetch(...);
          observer.next(jsonResponse);
          observer.complete();
        });
      };
    

    Now, if the scenario is that you want to reuse data from another query, then the situation with the cache might not apply for you. But If you are able to get the data from the store, you should be able to use the observable to pass data before you get the response from the server.

    Edit:

    Relay has added some new features since the original reply, and the same functionality can now be implemented like this:

    // Create relay environment
    const environment = new Environment({
      network: Network.create(/*...*/),
      store,
      gcReleaseBufferSize: 10, // Relay will cache the 10 latest responses
    });
    
    // Then set fetchPolicy on your QueryRenderer
    
    <QueryRenderer {...otherProps} fetchPolicy="store-and-network" />
    

    Relay will now use the response from the store if available, and when the response comes back from the server, use the response from the server.