tldr;
Use createOperationDescriptor
, and ask RelayEnvironment
for the data, rather than sending queryProps to a window object.
const Component = () => {
const environment = getRelayEnvironment(records);
const queryConcreteRequest = getRequest(someGraphQLQuery);
return (<SomeQueryAskingComponent {...environment.lookup(
createOperationDescriptor(
queryConcreteRequest,
someVariables,
).fragment,
).data}/>);
};
A lot of what I see online in terms of articles around Relay + SSR is that they all follow a pattern similar to this:
- They have a page level query ✅
- The page level query gets fetched on the server with a
fetchQuery
✅ - They then dump the store to a window object (for hydration) ✅
But where they all fall apart is when they also flush the queryProps to the window object that you'd typically give to the component. ❌
Before I tell you why that is wrong, lets look at the flow of how things work.
Relay has 2 parts really, you have a RelayEnvironment
, and a query (fragments, query renderes, etc..). The environment lives in a RelayEnvironmentProvider
so when you have a useFragment
or createFragementContainer
it creates an identifier, and resolves data from its props. Those props typically come from a queryRenderer
's render
prop, or in SSR world come directly from a fetchQuery
.
So the reason why the above is wrong is because if you have a massive page level query. The store is flushed to the window object, as well as the query props! Both contain duplicated bits of data. One being the map of ID
's, and one being the "resolved data" for your query.
Now, in a production app using Next.js as the example, there is a NEXT_DATA
, which is basically the window object constructed as a way for server side to relay (pun intended) initial props to the client side, for hydration. Now if you flushed both you end up with massive payloads. As an example, I had a blog that asked for authors, article bodies, tags, comments, reviews, related article's etc... All of that rolled into something like 46k lines of json (please just accept that it was large), which is horrible right!
Now lets get to the point of the article - How to fix this!
Instead of flushing the queryProps in the NEXT_DATA
. Just figure out a way to resolve the queryProps on the client using nothing but the store. It's simple really.
You need 2 things: a reference to the query itself, and a RelayEnvironment
. The query forms something of an "id" into the store, and the environment has the store. So create a relay store identifier through a createOperationDescriptor
, which takes the query and its variables and spits out an id. Then use that id to lookup the data in the store, and simply give that to the component. Happy days! And now you're left with, and from our example, an almost 1k line JSON. Some ridiculous savings there!
Here is an example of this:
import { fetchQuery, graphql } from 'react-relay';
import { createOperationDescriptor, getRequest } from 'relay-runtime';
const WithData = (PageComponent, options) => {
const WrappedComponent = ({ variables, records }) => {
const environment = getRelayEnvironment(records);
const queryConcreteRequest = getRequest(options.query);
const requestIdentifier = createOperationDescriptor(
queryConcreteRequest,
variables,
);
const pageData = environment.lookup(
requestIdentifier.fragment,
);
return <RelayEnvironmentProvider environment={environment}>
<PageComponent {...pageData.data}/>
</RelayEnvironmentProvider>;
};
WrappedComponent.getInitialProps = async () => {
const environment = getRelayEnvironment();
const variables = options.variables();
await fetchQuery(
environment,
options.query,
variables,
);
const records = environment
.getStore()
.getSource()
.toJSON();
return {
variables,
records,
};
};
return WrappedComponent;
};
export default withData(
({ article }) => <h1>{article.name}</h1>,
{
query: graphql`
query ArticleQuery($slug: String!) {
article(slug: $slug) {
name
}
}`,
variables() {
return { slug: 'example' };
},
});
Top comments (3)
does your
getRelayEnvironment
look like this: createRelayEnvironment?This is an adaptation of my
getRelayEnvironment
function: gist.github.com/maraisr/32a7b0c1a4....I'd like to change the fetch function though to return an Observable though, so that it can abort fetch's when components un-mount.
Outside that; the
withData
HoC outlined in my article is still very much the case for us.BUT! I will be updating this article in the next coming month to utilize the new
preloadQuery
andusePreloadedQuery
methods along withEntryPointContainer
from Relay.Thanks a lot!