Skip to main content
Version: v12.0.0

Refreshing Fragments

When referring to "refreshing a fragment", we mean fetching the exact same data that was originally rendered by the fragment, in order to get the most up-to-date version of that data from the server.

Using real-time features#

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.

Using useRefetchableFragment#

In order to manually refresh the data for a fragment, we need a query to refetch the fragment under; remember, fragments can't be fetched by themselves: they need to be part of a query, so we can't just "fetch" the fragment again by itself.

To do so, we can also use the useRefetchableFragment Hook in combination with the @refetchable directive, which will automatically generate a query to refetch the fragment under, and which we can fetch using the refetch function:

import type {UserComponent_user$key} from 'UserComponent_user.graphql';// This type is autogenerated by Relay given @refetchable used belowimport type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';
type Props = {  user: UserComponent_user$key,};
function UserComponent(props: Props) {  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(    graphql`      fragment UserComponent_user on User      # @refetchable makes it so Relay autogenerates a query for      # fetching this fragment      @refetchable(queryName: "UserComponentRefreshQuery") {        id        name        friends {          count        }      }    `,    props.user,  );
  const refresh = useCallback(() => {    // We call refetch with empty variables: `{}`,    // which will refetch the @refetchable query with the same    // original variables the fragment was fetched with, and update    // this component with the latest fetched data.    // The fetchPolicy ensures we always fetch from the server and skip    // the local data cache.    refetch({}, {fetchPolicy: 'network-only'})  }), [/* ... */];
  return (    <>      <h1>{data.name}</h1>      <div>Friends count: {data.friends?.count}</div>      <Button        onClick={() => refresh()}>        Fetch latest count      </Button>    </>  );}

Let's distill what's happening in this example:

  • useRefetchableFragment behaves similarly to useFragment (see the Fragments section), but with a few additions:
    • It expects a fragment that is annotated with the @refetchable directive. Note that @refetchable directive can only be added to fragments that are "refetchable", that is, on fragments that are on Viewer, on Query, on any type that implements Node (i.e. a type that has an id field).
  • It returns a refetch function, which is already Flow typed to expect the query variables that the generated query expects
  • It takes two Flow type parameters: the type of the generated query (in our case UserComponentRefreshQuery), and a second type which can always be inferred, so you only need to pass underscore (_).
  • We're calling the refetch function with 2 main inputs:
    • The first argument is the set of variables to fetch the fragment with. In this case, calling refetch and passing an empty set of variables will fetch the fragment again with the exact same variables the fragment was originally fetched with, which is what we want for a refresh.
    • In the second argument we are passing a fetchPolicy of 'network-only' to ensure that we always fetch from the network and skip the local data cache.
  • Calling refetch will re-render the component and cause useRefetchableFragment to suspend (as explained in Loading States with Suspense), since a network request will be required due to the fetchPolicy we are using. This means that you'll need to make sure that there's a Suspense boundary wrapping this component from above in order to show a fallback loading state.
info

Note that this same behavior also applies to using the refetch function from usePaginationFragment.

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 instead, and manually keep track of a loading state:

note

In future versions of React when concurrent rendering is supported, React will provide an option to support this case and avoid hiding already rendered content with a Suspense fallback when suspending.

import type {UserComponent_user$key} from 'UserComponent_user.graphql';// This type is autogenerated by Relay given @refetchable used belowimport type {UserComponentRefreshQuery} from 'UserComponentRefreshQuery.graphql';
type Props = {  user: UserComponent_user$key,};
function UserComponent(props: Props) {  const [data, refetch] = useRefetchableFragment<UserComponentRefreshQuery, _>(    graphql`      fragment UserComponent_user on User      # @refetchable makes it so Relay autogenerates a query for      # fetching this fragment      @refetchable(queryName: "UserComponentRefreshQuery") {        id        name        friends {          count        }      }    `,    props.user,  );
  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 call          // refetch again to re-render with the updated data.          // At this point the data for the query should          // be cached, so we use the 'store-only'          // fetchPolicy to avoid suspending.          refetch({}, {fetchPolicy: 'store-only'});        }        error: () => {          setIsRefreshing(false);        }      });  }), [/* ... */];
  return (    <>      <h1>{data.name}</h1>      <div>Friends count: {data.friends?.count}</div>      <Button        disabled={isRefreshing}        onClick={() => refresh()}>        Fetch latest count {isRefreshing ? <LoadingSpinner /> : null}      </Button>    </>  );}

Let's distill what's going on here:

  • When refreshing, we now keep track of our own isRefreshing loading state, since we are avoiding suspending. We can use this state to render a busy spinner or similar loading UI in our component, without hiding the content.
  • 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 refetch so that we render the updated data, similar to the previous example.
  • At this point, when refetch is called, the data for the fragment 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.

Is this page useful?