Prefetching
The goal of prefetching is to make data fetch before the user navigates to a page or attempts to load some known content.
There are a handful of situations that you may want to do this, but some very common use cases are:
- User hovers over a navigation element
- User hovers over a list element that is a link
- User hovers over a next pagination button
- User navigates to a page and you know that some components down the tree will require said data. This way, you can prevent fetching waterfalls.
Prefetching with React Hooks
Similar to the useMutation
hook, the usePrefetch
hook will not run automatically โ it returns a "trigger function" that can be used to initiate the behavior.
It accepts two arguments: the first is the key of a query action that you defined in your API service, and the second is an object of two optional parameters:
export type PrefetchOptions =
| { force?: boolean }
| {
ifOlderThan?: false | number;
};
usePrefetch<EndpointName extends QueryKeys<Definitions>>(
endpointName: EndpointName,
options?: PrefetchOptions
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
Customizing the Hook Behavior
You can specify these prefetch options when declaring the hook or at the call site. The call site will take priority over the defaults.
ifOlderThan
- (default:false
|number
) - number is value in seconds- If specified, it will only run the query if the difference between
new Date()
and the lastfulfilledTimeStamp
is greater than the given value
- If specified, it will only run the query if the difference between
force
- If
force: true
, it will ignore theifOlderThan
value if it is set and the query will be run even if it exists in the cache.
- If
Trigger Function Behavior
- The trigger function always returns
void
. - If
force: true
is set during the declaration or at the call site, the query will be run no matter what. The one exception to that is if the same query is already in-flight. - If no options are specified and the query exists in the cache, the query will not be performed.
- If no options are specified and the query does not exist in the cache, the query will be performed.
- Assuming you have a
useQuery
hook in the tree that is subscribed to the same query that you are prefetching:useQuery
will return{isLoading: true, isFetching: true, ...rest
}
- Assuming you have a
- If
ifOlderThan
is specified but evaluates to false and the query is in the cache, the query will not be performed. - If
ifOlderThan
is specified and evaluates to true, the query will be performed even if there is an existing cache entry.- Assuming you have a
useQuery
hook in the tree that is subscribed to the same query that you are prefetching:useQuery
will return{isLoading: false, isFetching: true, ...rest
}
- Assuming you have a
function User() {
const prefetchUser = usePrefetch('getUser')
// Low priority hover will not fire unless the last request happened more than 35s ago
// High priority hover will _always_ fire
return (
<div>
<button onMouseEnter={() => prefetchUser(4, { ifOlderThan: 35 })}>
Low priority
</button>
<button onMouseEnter={() => prefetchUser(4, { force: true })}>
High priority
</button>
</div>
)
}
Recipe: Prefetch Immediately
In some cases, you may want to prefetch a resource immediately. You can implement this in just a few lines of code:
type EndpointNames = keyof typeof api.endpoints
export function usePrefetchImmediately<T extends EndpointNames>(
endpoint: T,
arg: Parameters<typeof api.endpoints[T]['initiate']>[0],
options: PrefetchOptions = {}
) {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(api.util.prefetch(endpoint, arg as any, options))
}, [])
}
// In a component
usePrefetchImmediately('getUser', 5)
Prefetching Without Hooks
If you're not using the usePrefetch
hook, you can recreate the same behavior on your own in any framework.
When dispatching the prefetch
thunk as shown below you will see the same exact behavior as described here.
store.dispatch(
api.util.prefetch(endpointName, arg, { force: false, ifOlderThan: 10 })
)
You can also dispatch the query action, but you would be responsible for implementing any additional logic.
dispatch(api.endpoints[endpointName].initiate(arg, { forceRefetch: true }))
Prefetching Examples
Basic Prefetching
This is a very basic example that shows how you can prefetch when a user hovers over the next arrow. This is probably not the optimal solution, because if they hover, click, then change pages without moving their mouse, we wouldn't know to prefetch the next page because we wouldn't see the next onMouseEnter
event. In this case, you would need to handle this on your own. You could also consider automatically prefetching the next page...
Automatic Prefetching
Picking up on our last example, we automatically prefetch
the next page, giving the appearance of no network delay.
Prefetching All Known Pages
After the first query initialized by useQuery
runs, we automatically fetch all remaining pages.