UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

338 lines (282 loc) • 12 kB
--- id: refetching-queries-with-different-data title: Refetching Queries with Different Data slug: /guided-tour/refetching/refetching-queries-with-different-data/ description: Relay guide to refetching queries with different data keywords: - refetching - query --- import DocsRating from '@site/src/core/DocsRating'; import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal'; // @fb-only // @fb-only // @fb-only When referring to **"refetching a query"**, we mean fetching the query again for *different* data than was originally rendered by the query. For example, this might be to change a currently selected item, to render a different list of items than the one being shown, or more generally to transition the currently rendered content to show new or different content. ## When using `useQueryLoader` / `loadQuery` Similarly to [Refreshing Queries with `useQueryLoader`](../refreshing-queries/#when-using-usequeryloader--loadquery), we can also use the `useQueryLoader` Hook described in our [Fetching Queries for Render](../../rendering/queries/#fetching-queries-for-render) section, but this time passing *different query variables*: <FbInternalOnly> // @fb-only </FbInternalOnly> <OssOnly> ```js /** * App.react.js */ const AppQuery = require('__generated__/AppQuery.graphql'); function App(props: Props) { const variables = {id: '4'}; const [queryRef, loadQuery] = useQueryLoader( AppQuery, props.appQueryRef /* initial query ref */ ); const refetch = useCallback(() => { // Load the query again using the same original variables. // Calling loadQuery will update the value of queryRef. loadQuery({id: 'different-id'}); }, [/* ... */]); return ( <React.Suspense fallback="Loading query..."> <MainContent refetch={refetch} queryRef={queryRef} /> </React.Suspense> ); } ``` ```js /** * MainContent.react.js */ // Renders the preloaded query, given the query reference function MainContent(props) { const {refetch, queryRef} = props; const data = usePreloadedQuery( graphql` query AppQuery($id: ID!) { user(id: $id) { name friends { count } } } `, queryRef, ); return ( <> <h1>{data.user?.name}</h1> <div>Friends count: {data.user?.friends?.count}</div> <Button onClick={() => refetch()}> Fetch latest count </Button> </> ); } ``` Let's distill what's going on here: * We call `loadQuery` in the event handler for refetching, so the network request starts immediately, and then pass the `queryRef` to `usePreloadedQuery`, so it renders the updated data. * We are not passing a `fetchPolicy` to `loadQuery`, meaning that it will use the default value of `'store-or-network'`. We could provide a different policy in order to specify whether to use locally cached data (as we covered in [Reusing Cached Data For Render](../../reusing-cached-data/)). * Calling `loadQuery` will re-render the component and may cause `usePreloadedQuery` to suspend (as explained in [Loading States with Suspense](../../rendering/loading-states/)). This means that we'll need to make sure that there's a `Suspense` boundary wrapping the `MainContent` component, in order to show a fallback loading state. </OssOnly> ### If you need to avoid Suspense In some cases, you might want to avoid showing a Suspense fallback, which would hide the already rendered content. For these cases, you can use [`fetchQuery`](../../../api-reference/fetch-query/) instead, and manually keep track of a loading state: <FbInternalOnly> // @fb-only </FbInternalOnly> ```js /** * App.react.js */ const AppQuery = require('__generated__/AppQuery.graphql'); function App(props: Props) { const environment = useRelayEnvironment(); const [queryRef, loadQuery] = useQueryLoader( AppQuery, props.appQueryRef /* initial query ref */ ); const [isRefetching, setIsRefetching] = useState(false) const refetch = useCallback(() => { if (isRefetching) { return; } setIsRefetching(true); const variables = { id: 'different-id' }; // fetchQuery will fetch the query and write // the data to the Relay store. This will ensure // that when we re-render, the data is already // cached and we don't suspend fetchQuery(environment, AppQuery, variables) .subscribe({ complete: () => { setIsRefetching(false); // *After* the query has been fetched, we call // loadQuery again to re-render with a new // queryRef. // At this point the data for the query should // be cached, so we use the 'store-only' // fetchPolicy to avoid suspending. loadQuery(variables, {fetchPolicy: 'store-only'}); }, error: () => { setIsRefetching(false); } }); }, [/* ... */]); return ( <React.Suspense fallback="Loading query..."> <MainContent isRefetching={isRefetching} refetch={refetch} queryRef={queryRef} /> </React.Suspense> ); } ``` Let's distill what's going on here: * When refetching, we now keep track of our own `isRefetching` loading state, since we are avoiding suspending. We can use this state to render a busy spinner or similar loading UI inside the `MainContent` component, *without* hiding the `MainContent`. * In the event handler, we first call `fetchQuery`, which will fetch the query and write the data to the local Relay store. When the `fetchQuery` network request completes, we call `loadQuery` so that we obtain an updated `queryRef` that we then pass to `usePreloadedQuery` in order render the updated data, similar to the previous example. * At this point, when `loadQuery` is called, the data for the query should already be cached in the local Relay store, so we use `fetchPolicy` of `'store-only'` to avoid suspending and only read the already cached data. ## When using `useLazyLoadQuery` Similarly to [Refreshing Queries with `useLazyLoadQuery`](../refreshing-queries/#when-using-uselazyloadquery), we can also use the [`useLazyLoadQuery`](../../../api-reference/use-lazy-load-query/) Hook described in our [Lazily Fetching Queries during Render](../../rendering/queries/#lazily-fetching-queries-during-render) section, but this time passing *different query variables*: <FbInternalOnly> // @fb-only </FbInternalOnly> <OssOnly> ```js /** * App.react.js */ const AppQuery = require('__generated__/AppQuery.graphql'); function App(props: Props) { const [queryArgs, setQueryArgs] = useState({ options: {fetchKey: 0}, variables: {id: '4'}, }); const refetch = useCallback(() => { // Trigger a re-render of useLazyLoadQuery with new variables, // *and* an updated fetchKey. // The new fetchKey will ensure that the query is fully // re-evaluated and refetched. setQueryArgs(prev => ({ options: { fetchKey: (prev?.options.fetchKey ?? 0) + 1, }, variables: {id: 'different-id'} })); }, [/* ... */]); return ( <React.Suspense fallback="Loading query..."> <MainContent refetch={refetch} queryArgs={queryArgs} /> </React.Suspense> ); } ``` ```js /** * MainContent.react.js */ // Fetches and renders the query, given the fetch options function MainContent(props) { const {refetch, queryArgs} = props; const data = useLazyLoadQuery( graphql` query AppQuery($id: ID!) { user(id: $id) { name friends { count } } } `, queryArgs.variables, queryArgs.options, ); return ( <> <h1>{data.user?.name}</h1> <div>Friends count: {data.user.friends?.count}</div> <Button onClick={() => refetch()}> Fetch latest count </Button> </> ); } ``` Let's distill what's going on here: * We update the component in the event handler for refreshing by setting new query args in state. This will cause the `MainContent` component that uses `useLazyLoadQuery` to re-render with the new `variables` and `fetchKey`, and refetch the query upon rendering. * We are passing a new value of `fetchKey` which we increment on every update. Passing a new `fetchKey` to `useLazyLoadQuery` on every update will ensure that the query is fully re-evaluated and refetched. * We are not passing a new `fetchPolicy` to `useLazyLoadQuery`, meaning that it will use the default value of `'store-or-network'`. We could provide a different policy in order to specify whether to use locally cached data (as we covered in [Reusing Cached Data For Render](../../reusing-cached-data/)). * The state update in `refetch` will re-render the component and may cause the component to suspend (as explained in [Loading States with Suspense](../../rendering/loading-states/)). This means that we'll need to make sure that there's a `Suspense` boundary wrapping the `MainContent` component, in order to show a fallback loading state. </OssOnly> ### If you need to avoid Suspense In some cases, you might want to avoid showing a Suspense fallback, which would hide the already rendered content. For these cases, you can use [`fetchQuery`](../../../api-reference/fetch-query/) instead, and manually keep track of a loading state: <FbInternalOnly> // @fb-only </FbInternalOnly> ```js /** * App.react.js */ const AppQuery = require('__generated__/AppQuery.graphql'); function App(props: Props) { const environment = useRelayEnvironment(); const [isRefreshing, setIsRefreshing] = useState(false) const [queryArgs, setQueryArgs] = useState({ options: {fetchKey: 0, fetchPolicy: 'store-or-network'}, variables: {id: '4'}, }); const refetch = useCallback(() => { if (isRefreshing) { return; } setIsRefreshing(true); const variables = { id: 'different-id' }; // fetchQuery will fetch the query and write // the data to the Relay store. This will ensure // that when we re-render, the data is already // cached and we don't suspend fetchQuery(environment, AppQuery, variables) .subscribe({ complete: () => { setIsRefreshing(false); // *After* the query has been fetched, we update // our state to re-render with the new fetchKey // and fetchPolicy. // At this point the data for the query should // be cached, so we use the 'store-only' // fetchPolicy to avoid suspending. setQueryArgs(prev => ({ options: { fetchKey: (prev?.options.fetchKey ?? 0) + 1, fetchPolicy: 'store-only', }, variables, })); }, error: () => { setIsRefreshing(false); } }); }, [/* ... */]); return ( <React.Suspense fallback="Loading query..."> <MainContent isRefetching={isRefetching} refetch={refetch} queryArgs={queryArgs} /> </React.Suspense> ); } ``` Let's distill what's going on here: * When refetching, we now keep track of our own `isRefetching` loading state, since we are avoiding suspending. We can use this state to render a busy spinner or similar loading UI inside the `MainContent` component, *without* hiding the `MainContent`. * In the event handler, we first call `fetchQuery`, which will fetch the query and write the data to the local Relay store. When the `fetchQuery` network request completes, we update our state so that we re-render an updated `fetchKey` and `fetchPolicy` that we then pass to `useLazyLoadQuery` in order render the updated data, similar to the previous example. * At this point, when we update the state, the data for the query should already be cached in the local Relay store, so we use `fetchPolicy` of `'store-only'` to avoid suspending and only read the already cached data. <DocsRating />