Initial Query Data
Key points (written by a model)
- initialData prepopulates a query's cache if empty and skips the initial loading state, but it is persisted to the cache.
- By default initialData is treated as totally fresh, so with default staleTime 0 the query immediately refetches on mount.
- Passing a function as initialData runs it only once when the query is initialized, saving memory and CPU.
- When initial data comes from cache, pass the source query's dataUpdatedAt to initialDataUpdatedAt instead of an artificial staleTime.
- queryClient.getQueryState exposes state.dataUpdatedAt so you can conditionally decide whether cached data is fresh enough to use as initial data.
Model-written
contextclaimmethodresultanalysislimitation
There are many ways to supply initial data for a query to the cache before you need it:There are many ways to supply initial data for a query to the cache before you need it:Query cache initial data can be supplied in several ways The following lists present declarative and imperative approaches before it is neededQuery cache initial data can be supplied in several waysinitial data
- Declaratively:
- Provide
initialDatato a query to prepopulate its cache if emptyProvideinitialDatato a query to prepopulate its cache if emptyProvide initialData to prepopulate a query's cache if empty This declarative option fills the cache only when it holds no data yetProvide initialData to prepopulate a query's cache if emptyinitialData
- Imperatively:
- Prefetch the data using
queryClient.queryPrefetch data imperatively using queryClient.prefetchQuery Prefetching populates the cache ahead of time so the query mounts with dataPrefetch data imperatively using queryClient.prefetchQueryprefetchQuery - Manually place the data into the cache using
queryClient.setQueryDataManually place data into the cache with queryClient.setQueryData This writes a value directly into the cache for a given query keyManually place data into the cache with queryClient.setQueryDatasetQueryData
Using initialData to prepopulate a query
The config.initialData option prepopulates a query and skips initial loading
There may be times when you already have the initial data for a query available in your app and can simply provide it directly to your query. If and when this is the case, you can use the config.initialData option to set the initial data for a query and skip the initial loading state!There may be times when you already have the initial data for a query available in your app and can simply provide it directly to your query. If and when this is the case, you can use the config.initialData option to set the initial data for a query and skip the initial loading state!Use config.initialData to set initial data and skip initial loading When data is already available in the app, it can be handed directly to the queryUse config.initialData to set initial data and skip initial loadingconfig.initialData
IMPORTANT:
initialDatais persisted to the cache, so it is not recommended to provide placeholder, partial or incomplete data to this option and instead useplaceholderDataIMPORTANT:initialDatais persisted to the cache, so it is not recommended to provide placeholder, partial or incomplete data to this option and instead useplaceholderDatainitialData is persisted, so avoid placeholder or partial data Use placeholderData instead for incomplete values since initialData is cachedinitialData is persisted, so avoid placeholder or partial datainitialData persistence
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: initialTodos,
})staleTime and initialDataUpdatedAt
initialData is treated as fresh and interacts with staleTime and refetching
By default, initialData is treated as totally fresh, as if it were just fetched. This also means that it will affect how it is interpreted by the staleTime option.By default, initialData is treated as totally fresh, as if it were just fetched. This also means that it will affect how it is interpreted by the staleTime option.initialData is treated as totally fresh, as if just fetched This default affects how staleTime interprets the initial datainitialData is treated as totally fresh, as if just fetchedinitialData freshness
If you configure your query observer with
initialData, and nostaleTime(the defaultstaleTime: 0), the query will immediately refetch when it mounts:If you configure your query observer withinitialData, and nostaleTime(the defaultstaleTime: 0), the query will immediately refetch when it mounts:With default staleTime 0, initialData queries refetch on mount The data is immediately considered stale, triggering a refetch when the query mountsWith default staleTime 0, initialData queries refetch on mountstaleTime 0// Will show initialTodos immediately, but also immediately refetch todos after mount const result = useQuery({ queryKey: ['todos'], queryFn: () => fetch('/todos'), initialData: initialTodos, })If you configure your query observer with
initialDataand astaleTimeof1000ms, the data will be considered fresh for that same amount of time, as if it was just fetched from your query function.If you configure your query observer withinitialDataand astaleTimeof1000ms, the data will be considered fresh for that same amount of time, as if it was just fetched from your query function.A staleTime of 1000 ms keeps initialData fresh that long The data is treated as if just fetched from the query function for that durationA staleTime of 1000 ms keeps initialData fresh that longstaleTime 1000 ms// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms const result = useQuery({ queryKey: ['todos'], queryFn: () => fetch('/todos'), initialData: initialTodos, staleTime: 1000, })So what if your
initialDataisn't totally fresh? That leaves us with the last configuration that is actually the most accurate and uses an option calledinitialDataUpdatedAt. This option allows you to pass a numeric JS timestamp in milliseconds of when the initialData itself was last updated, e.g. whatDate.now()provides. Take note that if you have a unix timestamp, you'll need to convert it to a JS timestamp by multiplying it by1000.That leaves us with the last configuration that is actually the most accurate and uses an option calledinitialDataUpdatedAt. This option allows you to pass a numeric JS timestamp in milliseconds of when the initialData itself was last updated, e.g. whatDate.now()provides. Take note that if you have a unix timestamp, you'll need to convert it to a JS timestamp by multiplying it by1000.initialDataUpdatedAt takes a JS timestamp of when data was last updated Unix timestamps must be multiplied by 1000 to convert to a JS timestampinitialDataUpdatedAt takes a JS timestamp of when data was last updatedinitialDataUpdatedAt// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms const result = useQuery({ queryKey: ['todos'], queryFn: () => fetch('/todos'), initialData: initialTodos, staleTime: 60 * 1000, // 1 minute // This could be 10 seconds ago or 10 minutes ago initialDataUpdatedAt: initialTodosUpdatedTimestamp, // eg. 1608412420052 })This option allows the staleTime to be used for its original purpose, determining how fresh the data needs to be, while also allowing the data to be refetched on mount if the
initialDatais older than thestaleTime. In the example above, our data needs to be fresh within 1 minute, and we can hint to the query when the initialData was last updated so the query can decide for itself whether the data needs to be refetched again or not.If you would rather treat your data as prefetched data, we recommend that you use the
queryapi to populate the cache beforehand, thus letting you configure yourstaleTimeindependently from yourinitialData.
Initial Data Function
A function as initialData runs once on query initialization, saving CPU
If the process for accessing a query's initial data is intensive or just not something you want to perform on every render, you can pass a function as the initialData value. This function will be executed only once when the query is initialized, saving you precious memory and/or CPU:If the process for accessing a query's initial data is intensive or just not something you want to perform on every render, you can pass a function as the initialData value. This function will be executed only once when the query is initialized, saving you precious memory and/or CPU:Pass a function as initialData to run it only once on init This saves memory and CPU when accessing initial data is intensive or render-avoidantPass a function as initialData to run it only once on initinitialData function
const result = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/todos'),
initialData: () => getExpensiveTodos(),
})Initial Data from Cache
Initial data can be derived from another query's cached result
In some circumstances, you may be able to provide the initial data for a query from the cached result of another. A good example of this would be searching the cached data from a todos list query for an individual todo item, then using that as the initial data for your individual todo query:In some circumstances, you may be able to provide the initial data for a query from the cached result of another. A good example of this would be searching the cached data from a todos list query for an individual todo item, then using that as the initial data for your individual todo query:Initial data can come from another query's cached result For example, search a todos list cache for an individual todo itemInitial data can come from another query's cached resultcached result
const result = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetch('/todos'),
initialData: () => {
// Use a todo from the 'todos' query as the initial data for this todo query
return queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId)
},
})Initial Data from the cache with initialDataUpdatedAt
Pass the source query's dataUpdatedAt to initialDataUpdatedAt instead of staleTime
Getting initial data from the cache means the source query you're using to look up the initial data from is likely old. Instead of using an artificial staleTime to keep your query from refetching immediately, it's suggested that you pass the source query's dataUpdatedAt to initialDataUpdatedAt. This provides the query instance with all the information it needs to determine if and when the query needs to be refetched, regardless of initial data being provided.Getting initial data from the cache means the source query you're using to look up the initial data from is likely old. Instead of using an artificial staleTime to keep your query from refetching immediately, it's suggested that you pass the source query's dataUpdatedAt to initialDataUpdatedAt. This provides the query instance with all the information it needs to determine if and when the query needs to be refetched, regardless of initial data being provided.Pass the source query's dataUpdatedAt to initialDataUpdatedAt This lets the query decide if and when to refetch rather than faking staleTimePass the source query's dataUpdatedAt to initialDataUpdatedAtdataUpdatedAt
const result = useQuery({
queryKey: ['todos', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () =>
queryClient.getQueryData(['todos'])?.find((d) => d.id === todoId),
initialDataUpdatedAt: () =>
queryClient.getQueryState(['todos'])?.dataUpdatedAt,
})Conditional Initial Data from Cache
getQueryState lets you conditionally use cached data only if fresh enough
If the source query you're using to look up the initial data from is old, you may not want to use the cached data at all and just fetch from the server. To make this decision easier, you can use the queryClient.getQueryState method instead to get more information about the source query, including a state.dataUpdatedAt timestamp you can use to decide if the query is "fresh" enough for your needs:If the source query you're using to look up the initial data from is old, you may not want to use the cached data at all and just fetch from the server. To make this decision easier, you can use the queryClient.getQueryState method instead to get more information about the source query, including a state.dataUpdatedAt timestamp you can use to decide if the query is "fresh" enough for your needs:Use queryClient.getQueryState to conditionally use cached data Its state.dataUpdatedAt timestamp tells if the source query is fresh enoughUse queryClient.getQueryState to conditionally use cached datagetQueryState
const result = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetch(`/todos/${todoId}`),
initialData: () => {
// Get the query state
const state = queryClient.getQueryState(['todos'])
// If the query exists and has data that is no older than 10 seconds...
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
// return the individual todo
return state.data.find((d) => d.id === todoId)
}
// Otherwise, return undefined and let it fetch from a hard loading state!
},
})Further reading
TkDodo's article compares Initial Data and Placeholder Data
For a comparison between Initial Data and Placeholder Data, see the article by TkDodo.For a comparison between Initial Data and Placeholder Data, see the article by TkDodo.TkDodo's article compares Initial Data and Placeholder Data It offers further reading on the difference between the two optionsTkDodo's article compares Initial Data and Placeholder DataInitial Data