website/versioned_docs/version-v14.0.0/guided-tour/refetching/refetching-fragments-with-different-data.md
import DocsRating from '@site/src/core/DocsRating'; import {OssOnly, FbInternalOnly} from 'docusaurus-plugin-internaldocs-fb/internal';
import OssAvoidSuspenseNote from './OssAvoidSuspenseNote.md';
When referring to "refetching a fragment", we mean fetching a different version of the data than the one was originally rendered by the fragment. 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.
Conceptually, this means fetching and rendering the currently rendered fragment again, but under a new query with different variables; or in other words, rendering the fragment under a new query root. Remember that 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.
useRefetchableFragmentTo 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 {CommentBodyRefetchQuery} from 'CommentBodyRefetchQuery.graphql';
import type {CommentBody_comment$key} from 'CommentBody_comment.graphql';
type Props = {
comment: CommentBody_comment$key,
};
function CommentBody(props: Props) {
const [data, refetch] = useRefetchableFragment<CommentBodyRefetchQuery, _>(
graphql`
fragment CommentBody_comment on Comment
# @refetchable makes it so Relay autogenerates a query for
# fetching this fragment
@refetchable(queryName: "CommentBodyRefetchQuery") {
body(lang: $lang) {
text
}
}
`,
props.comment,
);
const refetchTranslation = () => {
// We call refetch with new variables,
// which will refetch the @refetchable query with the
// new variables and update this component with the
// latest fetched data.
refetch({lang: 'SPANISH'});
};
return (
<>
<p>{data.body?.text}</p>
<Button
onClick={() => refetchTranslation()}>
Translate Comment
</Button>
</>
);
}
Let's distill what's happening in this example:
useRefetchableFragment behaves similarly to useFragment (see the Fragments section), but with a few additions:
@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), or on a @fetchable type.refetch function, which is already Flow-typed to expect the query variables that the generated query expects.CommentBodyRefetchQuery), and a second type which can always be inferred, so you only need to pass underscore (_).refetch function with 2 main inputs:
refetch and passing a new set of variables will fetch the fragment again with the newly provided variables. The variables you need to provide are a subset of the variables that the @refetchable query expects; the query will require an id, if the type of the fragment has an id field, and any other variables that are transitively referenced in your fragment.
id and a new value for the translationType variable to fetch the translated comment body.fetchPolicy of 'store-or-network', which will skip the network request if the new data for that fragment is already cached (as we covered in Reusing Cached Data For Render).refetch will re-render the component and may cause useRefetchableFragment to suspend (as explained in Loading States with Suspense). 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.
:::
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:
import type {CommentBodyRefetchQuery} from 'CommentBodyRefetchQuery.graphql';
import type {CommentBody_comment$key} from 'CommentBody_comment.graphql';
type Props = {
comment: CommentBody_comment$key,
};
function CommentBody(props: Props) {
const [data, refetch] = useRefetchableFragment<CommentBodyRefetchQuery, _>(
graphql`
fragment CommentBody_comment on Comment
# @refetchable makes it so Relay autogenerates a query for
# fetching this fragment
@refetchable(queryName: "CommentBodyRefetchQuery") {
body(lang: $lang) {
text
}
}
`,
props.comment,
);
const [isRefetching, setIsRefreshing] = useState(false)
const refetchTranslation = () => {
if (isRefetching) { 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({lang: 'SPANISH'}, {fetchPolicy: 'store-only'});
}
error: () => {
setIsRefreshing(false);
}
});
};
return (
<>
<p>{data.body?.text}</p>
<Button
disabled={isRefetching}
onClick={() => refetchTranslation()}>
Translate Comment {isRefetching ? <LoadingSpinner /> : null}
</Button>
</>
);
}
Let's distill what's going on here:
isRefetching 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.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.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.