Skip to main content

generated-data-loaders

Here's an improved version of your document:


Generated Data Loaders

To minimize repetitive work, we automatically generate Remix data loaders by analyzing the page source code. Our parser scans the page for GraphQL queries and calls all of those queries in the generated data loaders. This ensures that by the time the component requests the data, it is already available in the Apollo Cache, reducing redundancy.

The following example demonstrates how a component's code is used to generate data loaders:

#Teams.tsx

const Teams = (props) => {
const [data, { loading }] = useTeamsQuery({ variables: { orgName, pageSize: 10 } });
// rest of the business logic
return (
// component render Markup
)
}

When our Rollup plugin parses the component and detects the GraphQL queries, it creates a key-value pair where the key represents the GraphQL document the query uses, and the value refers to the variables used by the query. Note the following:

  • The GraphQL document must be present in the core package. For example, if a component is inside account-browser, the GraphQL document should be exported from either the client or core package.
  • Variables passed in the variables parameter will be converted to params.<variableName>, and the loader will expect these values in the params.

For the above component, the following routes.json structure will be created:

{
"queries": {
"TeamsDocument": "{orgName: params.orgName, pageSize: 10}"
}
}

This serves as meta-information for our generator function, which uses it to create loaders for both server and client. The generated loaders will look something like this:

export const loaders = ({ params, context }) => {
const { apolloClient: client } = context;
const queries = { GetTeamsDocument: { orgName: params.orgName, pageSize: 10 } };

const organizationTeamsQuery = client.query({
query: GetTeamsDocument,
variables: queries['GetTeamsDocument'],
fetchPolicy: __SERVER__ ? 'network-only' : 'cache-first',
});

return {
data: {
organizationTeamsQuery
}
};
}

const clientLoader = async ({ params, serverLoader }) => {
const client = window.__APOLLO_CLIENT__;
try {
const getKey = (documentName) => camelCase(documentName).replace('Document', '');
const queries = { GetTeamsDocument: { orgName: params.orgName, pageSize: 10 } };
const queryKeys = ['TeamsQuery'];
let shouldCallServerLoader = false;
let response = {};
let cachedData, cacheKey;

cachedData = client.cache.readQuery({
query: GetTeamsDocument,
variables: queries['GetTeamsDocument'],
});
cacheKey = getKey('GetTeamsDocument');

if (!cachedData) {
shouldCallServerLoader = true;
}
if (cachedData && cachedData[cacheKey]) {
response[queryKeys[0]] = Promise.resolve(cachedData[cacheKey]);
}

if (!shouldCallServerLoader) return response;
const serverData = await serverLoader();
let queryKey = queryKeys[0];

if (!serverData[queryKey].then) {
return;
}
serverData[queryKey].then(({ data }) => {
client.cache.writeQuery({
query: GetTeamsDocument,
variables: queries['GetTeamsDocument'],
data,
});
});

return serverData;
} catch (err) {
console.error('Error in clientLoader', err);
}
}

The above document provides an example for a simple use case where a component has only one query. For components with multiple queries, the generated loaders will be longer.