-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Description
Describe the bug
When passing two queries with the same query key to useQueries, with a select function and a non-object value as placeholder data, the data of both entries in the query results array will be the value of the first call to select.
const results = useQueries({
queries: [
{ queryKey: ["abc"], queryFn: () => fetchSomething(), placeholderData: null, select: data => ({ id: 1, data }) },
{ queryKey: ["abc"], queryFn: () => fetchSomething(), placeholderData: null, select: data => ({ id: 2, data }) },
]
})
// prints { id: 1, data: null }
console.log(results[0].data)
// prints { id: 1, data: null }, but id should be 2
console.log(results[1].data)This causes a "duplicate key" warning in React, because the id is the same for two items.
Once the fetch is completed the data is correct. It also works if you provide an object value (e.g. {}).
Your minimal, reproducible example
https://codesandbox.io/s/gracious-solomon-pvbv9m
Steps to reproduce
- Run the following code in a component.
const results = useQueries({
queries: [
{
queryKey: ["abc"],
queryFn: () => new Promise(() => {}),
placeholderData: null,
select: (data) => ({ id: 1, data }),
enabled: false
},
{
queryKey: ["abc"],
queryFn: () => new Promise(() => {}),
placeholderData: null,
select: (data) => ({ id: 2, data }),
enabled: false
}
]
});
console.log(results[0].data)
console.log(results[1].data)- Check the output in the console.
Expected behavior
As a user, I expected the data returned for each query to be mapped using the select-function.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: macOS
- Browser: Firefox Developer Edition
- Version: 105.0b9
react-query version
v4.3.9
TypeScript version
v4.1.3
Additional context
In our app, we are fetching time series data for some charts based on user configuration. The configuration consists of some UI-stuff (e.g. name and color of the time series) and a query used to fetch the time series. It looks roughly like this:
interface ChartConfig {
series: TimeSeriesConfig[]
}
interface TimeSeriesConfig {
label: string
color: string
query: { metric: string, method: string }
}The user has complete freedom to configure their charts, so it is possible for them to include the exact same query in both other charts and in the same chart. The labels and colors however might vary, so we only want data to be cached on the actual query.
We use useQueries to fetch the data and then merge the config with the time series data using select:
const results = useQueries({
queries: configs.map(config => ({
queryKey: ['time-series', config.query],
queryFn: () => fetchTimeSeries(config.query),
placeholderData: null,
select: data => ({
...config,
data
})
})
})In some cases, there might not be any time series data available which we signal by returning null from fetchTimeSeries. To allow some things to render before the time series data is fetched, we set placeholderData to null.
Workaround
Since it works correctly when placeholderData is an object, one workaround is to wrap the result in an discriminated union and provide an empty case as placeholderData:
const results = useQueries({
queries: configs.map(config => ({
queryKey: ['time-series', config.query],
queryFn: () => fetchTimeSeries(config.query).then(data => ({ fetched: true, data }),
placeholderData: { fetched: false },
select: result => ({
...config,
data: result.fetched ? result.data : null
})
})
})