website/versioned_docs/version-v20.0.0/guided-tour/refetching/refreshing-queries.md
import DocsRating from '@site/src/core/DocsRating'; import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
When referring to "refreshing a query", we mean fetching the exact same data that was originally rendered by the query, in order to get the most up-to-date version of that data from the server.
If we want to keep our data up to date with the latest version from the server, the first thing to consider is if it appropriate to use any real-time features, which can make it easier to automatically keep the data up to date without manually refreshing the data periodically.
One example of this is using GraphQL Subscriptions, which will require additional configuration on your server and network layer.
</OssOnly>useQueryLoader / loadQueryTo refresh a query using the useQueryLoader Hook described in our Fetching Queries for Render section, we only need to call loadQuery again:
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const [queryRef, loadQuery] = useQueryLoader(
AppQuery,
props.appQueryRef /* initial query ref */
);
const refresh = useCallback(() => {
// Load the query again using the same original variables.
// Calling loadQuery will update the value of queryRef.
// The fetchPolicy ensures we always fetch from the server and skip
// the local data cache.
const {variables} = props.appQueryRef;
loadQuery(variables, {fetchPolicy: 'network-only'});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
refresh={refresh}
queryRef={queryRef}
/>
</React.Suspense>
);
}
/**
* MainContent.react.js
*/
// Renders the preloaded query, given the query reference
function MainContent(props) {
const {refresh, 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={() => refresh()}>
Fetch latest count
</Button>
</>
);
}
Let's distill what's going on here:
loadQuery in the event handler for refreshing, so the network request starts immediately, and then pass the updated queryRef to the MainContent component that uses usePreloadedQuery, so it renders the updated data.fetchPolicy of 'network-only' to ensure that we always fetch from the network and skip the local data cache.loadQuery will re-render the component and cause usePreloadedQuery to suspend (as explained in Loading States with Suspense), since a network request will always be made due to the fetchPolicy we are using. 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.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 instead, and manually keep track of a loading state:
/**
* 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 [isRefreshing, setIsRefreshing] = useState(false)
const refresh = useCallback(() => {
if (isRefreshing) { return; }
const {variables} = props.appQueryRef;
setIsRefreshing(true);
// 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 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: () => {
setIsRefreshing(false);
}
});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefreshing={isRefreshing}
refresh={refresh}
queryRef={queryRef}
/>
</React.Suspense>
);
}
Let's distill what's going on here:
isRefreshing 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.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.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.useLazyLoadQueryTo refresh a query using the useLazyLoadQuery Hook described in our Lazily Fetching Queries during Render section, we can do the following:
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const variables = {id: '4'};
const [refreshedQueryOptions, setRefreshedQueryOptions] = useState(null);
const refresh = useCallback(() => {
// Trigger a re-render of useLazyLoadQuery with the same variables,
// but an updated fetchKey and fetchPolicy.
// The new fetchKey will ensure that the query is fully
// re-evaluated and refetched.
// The fetchPolicy ensures that we always fetch from the network
// and skip the local data cache.
setRefreshedQueryOptions(prev => ({
fetchKey: (prev?.fetchKey ?? 0) + 1,
fetchPolicy: 'network-only',
}));
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
refresh={refresh}
queryOptions={refreshedQueryOptions ?? {}}
variables={variables}
/>
</React.Suspense>
);
/**
* MainContent.react.js
*/
// Fetches and renders the query, given the fetch options
function MainContent(props) {
const {refresh, queryOptions, variables} = props;
const data = useLazyLoadQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
friends {
count
}
}
}
`,
variables,
queryOptions,
);
return (
<>
<h1>{data.user?.name}</h1>
<div>Friends count: {data.user.friends?.count}</div>
<Button
onClick={() => refresh()}>
Fetch latest count
</Button>
</>
);
}
Let's distill what's going on here:
MainContent component that uses useLazyLoadQuery to re-render with the new fetchKey and fetchPolicy, and refetch the query upon rendering.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.fetchPolicy of 'network-only' to ensure that we always fetch from the network and skip the local data cache.refresh will cause the component to suspend (as explained in Loading States with Suspense), since a network request will always be made due to the fetchPolicy we are using. 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.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 instead, and manually keep track of a loading state:
/**
* App.react.js
*/
import type {AppQuery as AppQueryType} from 'AppQuery.graphql';
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const variables = {id: '4'}
const environment = useRelayEnvironment();
const [refreshedQueryOptions, setRefreshedQueryOptions] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(false)
const refresh = useCallback(() => {
if (isRefreshing) { return; }
setIsRefreshing(true);
// 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.
setRefreshedQueryOptions(prev => ({
fetchKey: (prev?.fetchKey ?? 0) + 1,
fetchPolicy: 'store-only',
}));
}
error: () => {
setIsRefreshing(false);
}
});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefreshing={isRefreshing}
refresh={refresh}
queryOptions={refreshedQueryOptions ?? {}}
variables={variables}
/>
</React.Suspense>
);
}
Let's distill what's going on here:
isRefreshing 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.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.fetchPolicy of 'store-only' to avoid suspending and only read the already cached data.