Skip to content

Commit 2624c42

Browse files
authored
2919 query key array (#2988)
* feat: query key array remove code that internally ensures that we get an Array, because it is now the expected interface, ensured by TypeScript * feat: query key array update tests to the new syntax * feat: query key array fix assertions, because there is no array wrapping happening internally anymore. The key you receive from the context is exactly the key you passed in * feat: query key array this test doesn't make much sense anymore * feat: query key array wrapping in an extra array doesn't yield the same results anymore since v4 because keys need to be an array * feat: query key array make docs adhere to new array key syntax * feat: query key array migration docs
1 parent a090fe5 commit 2624c42

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+331
-333
lines changed

docs/src/pages/guides/caching.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ This caching example illustrates the story and lifecycle of:
1616

1717
Let's assume we are using the default `cacheTime` of **5 minutes** and the default `staleTime` of `0`.
1818

19-
- A new instance of `useQuery('todos', fetchTodos)` mounts.
19+
- A new instance of `useQuery(['todos'], fetchTodos)` mounts.
2020
- Since no other queries have been made with this query + variable combination, this query will show a hard loading state and make a network request to fetch the data.
21-
- It will then cache the data using `'todos'` and `fetchTodos` as the unique identifiers for that cache.
21+
- It will then cache the data using `['todos']` as the unique identifiers for that cache.
2222
- The hook will mark itself as stale after the configured `staleTime` (defaults to `0`, or immediately).
23-
- A second instance of `useQuery('todos', fetchTodos)` mounts elsewhere.
23+
- A second instance of `useQuery(['todos'], fetchTodos)` mounts elsewhere.
2424
- Because this exact data exists in the cache from the first instance of this query, that data is immediately returned from the cache.
2525
- A background refetch is triggered for both queries (but only one request), since a new instance appeared on screen.
2626
- Both instances are updated with the new data if the fetch is successful
27-
- Both instances of the `useQuery('todos', fetchTodos)` query are unmounted and no longer in use.
27+
- Both instances of the `useQuery(['todos'], fetchTodos)` query are unmounted and no longer in use.
2828
- Since there are no more active instances of this query, a cache timeout is set using `cacheTime` to delete and garbage collect the query (defaults to **5 minutes**).
29-
- Before the cache timeout has completed another instance of `useQuery('todos', fetchTodos)` mounts. The query immediately returns the available cached value while the `fetchTodos` function is being run in the background to populate the query with a fresh value.
30-
- The final instance of `useQuery('todos', fetchTodos)` unmounts.
31-
- No more instances of `useQuery('todos', fetchTodos)` appear within **5 minutes**.
29+
- Before the cache timeout has completed another instance of `useQuery(['todos'], fetchTodos)` mounts. The query immediately returns the available cached value while the `fetchTodos` function is being run in the background to populate the query with a fresh value.
30+
- The final instance of `useQuery(['todos'], fetchTodos)` unmounts.
31+
- No more instances of `useQuery(['todos'], fetchTodos)` appear within **5 minutes**.
3232
- This query and its data are deleted and garbage collected.

docs/src/pages/guides/default-query-function.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ If you find yourself wishing for whatever reason that you could just share the s
77

88
```js
99
// Define a default query function that will receive the query key
10-
// the queryKey is guaranteed to be an Array here
1110
const defaultQueryFn = async ({ queryKey }) => {
1211
const { data } = await axios.get(`https://jsonplaceholder.typicode.com${queryKey[0]}`);
1312
return data;
@@ -32,14 +31,14 @@ function App() {
3231

3332
// All you have to do now is pass a key!
3433
function Posts() {
35-
const { status, data, error, isFetching } = useQuery('/posts')
34+
const { status, data, error, isFetching } = useQuery(['/posts'])
3635

3736
// ...
3837
}
3938

4039
// You can even leave out the queryFn and just go straight into options
4140
function Post({ postId }) {
42-
const { status, data, error, isFetching } = useQuery(`/posts/${postId}`, {
41+
const { status, data, error, isFetching } = useQuery([`/posts/${postId}`], {
4342
enabled: !!postId,
4443
})
4544

docs/src/pages/guides/disabling-queries.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function Todos() {
2626
error,
2727
refetch,
2828
isFetching,
29-
} = useQuery('todos', fetchTodoList, {
29+
} = useQuery(['todos'], fetchTodoList, {
3030
enabled: false,
3131
})
3232

docs/src/pages/guides/filters.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ A query filter is an object with certain conditions to match a query with:
1414
await queryClient.cancelQueries()
1515

1616
// Remove all inactive queries that begin with `posts` in the key
17-
queryClient.removeQueries('posts', { type: 'inactive' })
17+
queryClient.removeQueries(['posts'], { type: 'inactive' })
1818

1919
// Refetch all active queries
2020
await queryClient.refetchQueries({ type: 'active' })
2121

2222
// Refetch all active queries that begin with `posts` in the key
23-
await queryClient.refetchQueries('posts', { type: 'active' })
23+
await queryClient.refetchQueries(['posts'], { type: 'active' })
2424
```
2525

2626
A query filter object supports the following properties:
@@ -51,7 +51,7 @@ A mutation filter is an object with certain conditions to match a mutation with:
5151
await queryClient.isMutating()
5252

5353
// Filter mutations by mutationKey
54-
await queryClient.isMutating({ mutationKey: "post" })
54+
await queryClient.isMutating({ mutationKey: ["post"] })
5555

5656
// Filter mutations using a predicate function
5757
await queryClient.isMutating({ predicate: (mutation) => mutation.options.variables?.id === 1 })

docs/src/pages/guides/infinite-queries.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function Projects() {
5656
isFetching,
5757
isFetchingNextPage,
5858
status,
59-
} = useInfiniteQuery('projects', fetchProjects, {
59+
} = useInfiniteQuery(['projects'], fetchProjects, {
6060
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
6161
})
6262

@@ -100,7 +100,7 @@ When an infinite query becomes `stale` and needs to be refetched, each group is
100100
If you only want to actively refetch a subset of all pages, you can pass the `refetchPage` function to `refetch` returned from `useInfiniteQuery`.
101101

102102
```js
103-
const { refetch } = useInfiniteQuery('projects', fetchProjects, {
103+
const { refetch } = useInfiniteQuery(['projects'], fetchProjects, {
104104
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
105105
})
106106

@@ -132,7 +132,7 @@ function Projects() {
132132
isFetchingNextPage,
133133
fetchNextPage,
134134
hasNextPage,
135-
} = useInfiniteQuery('projects', fetchProjects, {
135+
} = useInfiniteQuery(['projects'], fetchProjects, {
136136
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
137137
})
138138

@@ -146,7 +146,7 @@ function Projects() {
146146
Bi-directional lists can be implemented by using the `getPreviousPageParam`, `fetchPreviousPage`, `hasPreviousPage` and `isFetchingPreviousPage` properties and functions.
147147

148148
```js
149-
useInfiniteQuery('projects', fetchProjects, {
149+
useInfiniteQuery(['projects'], fetchProjects, {
150150
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
151151
getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
152152
})
@@ -157,7 +157,7 @@ useInfiniteQuery('projects', fetchProjects, {
157157
Sometimes you may want to show the pages in reversed order. If this is case, you can use the `select` option:
158158

159159
```js
160-
useInfiniteQuery('projects', fetchProjects, {
160+
useInfiniteQuery(['projects'], fetchProjects, {
161161
select: data => ({
162162
pages: [...data.pages].reverse(),
163163
pageParams: [...data.pageParams].reverse(),
@@ -170,7 +170,7 @@ useInfiniteQuery('projects', fetchProjects, {
170170
Manually removing first page:
171171

172172
```js
173-
queryClient.setQueryData('projects', data => ({
173+
queryClient.setQueryData(['projects'], data => ({
174174
pages: data.pages.slice(1),
175175
pageParams: data.pageParams.slice(1),
176176
}))
@@ -183,7 +183,7 @@ const newPagesArray = oldPagesArray?.pages.map((page) =>
183183
page.filter((val) => val.id !== updatedId)
184184
) ?? []
185185

186-
queryClient.setQueryData('projects', data => ({
186+
queryClient.setQueryData(['projects'], data => ({
187187
pages: newPagesArray,
188188
pageParams: data.pageParams,
189189
}))

docs/src/pages/guides/initial-query-data.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ There may be times when you already have the initial data for a query available
1919
2020
```js
2121
function Todos() {
22-
const result = useQuery('todos', () => fetch('/todos'), {
22+
const result = useQuery(['todos'], () => fetch('/todos'), {
2323
initialData: initialTodos,
2424
})
2525
}
@@ -34,7 +34,7 @@ By default, `initialData` is treated as totally fresh, as if it were just fetche
3434
```js
3535
function Todos() {
3636
// Will show initialTodos immediately, but also immediately refetch todos after mount
37-
const result = useQuery('todos', () => fetch('/todos'), {
37+
const result = useQuery(['todos'], () => fetch('/todos'), {
3838
initialData: initialTodos,
3939
})
4040
}
@@ -45,7 +45,7 @@ By default, `initialData` is treated as totally fresh, as if it were just fetche
4545
```js
4646
function Todos() {
4747
// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms
48-
const result = useQuery('todos', () => fetch('/todos'), {
48+
const result = useQuery(['todos'], () => fetch('/todos'), {
4949
initialData: initialTodos,
5050
staleTime: 1000,
5151
})
@@ -56,7 +56,7 @@ By default, `initialData` is treated as totally fresh, as if it were just fetche
5656
```js
5757
function Todos() {
5858
// Show initialTodos immediately, but won't refetch until another interaction event is encountered after 1000 ms
59-
const result = useQuery('todos', () => fetch('/todos'), {
59+
const result = useQuery(['todos'], () => fetch('/todos'), {
6060
initialData: initialTodos,
6161
staleTime: 60 * 1000 // 1 minute
6262
// This could be 10 seconds ago or 10 minutes ago
@@ -74,7 +74,7 @@ If the process for accessing a query's initial data is intensive or just not som
7474

7575
```js
7676
function Todos() {
77-
const result = useQuery('todos', () => fetch('/todos'), {
77+
const result = useQuery(['todos'], () => fetch('/todos'), {
7878
initialData: () => {
7979
return getExpensiveTodos()
8080
},
@@ -91,7 +91,7 @@ function Todo({ todoId }) {
9191
const result = useQuery(['todo', todoId], () => fetch('/todos'), {
9292
initialData: () => {
9393
// Use a todo from the 'todos' query as the initial data for this todo query
94-
return queryClient.getQueryData('todos')?.find(d => d.id === todoId)
94+
return queryClient.getQueryData(['todos'])?.find(d => d.id === todoId)
9595
},
9696
})
9797
}
@@ -105,9 +105,9 @@ Getting initial data from the cache means the source query you're using to look
105105
function Todo({ todoId }) {
106106
const result = useQuery(['todo', todoId], () => fetch(`/todos/${todoId}`), {
107107
initialData: () =>
108-
queryClient.getQueryData('todos')?.find(d => d.id === todoId),
108+
queryClient.getQueryData(['todos'])?.find(d => d.id === todoId),
109109
initialDataUpdatedAt: () =>
110-
queryClient.getQueryState('todos')?.dataUpdatedAt,
110+
queryClient.getQueryState(['todos'])?.dataUpdatedAt,
111111
})
112112
}
113113
```
@@ -121,7 +121,7 @@ function Todo({ todoId }) {
121121
const result = useQuery(['todo', todoId], () => fetch(`/todos/${todoId}`), {
122122
initialData: () => {
123123
// Get the query state
124-
const state = queryClient.getQueryState('todos')
124+
const state = queryClient.getQueryState(['todos'])
125125

126126
// If the query exists and has data that is no older than 10 seconds...
127127
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {

docs/src/pages/guides/invalidations-from-mutations.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const queryClient = useQueryClient()
2121
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
2222
const mutation = useMutation(addTodo, {
2323
onSuccess: () => {
24-
queryClient.invalidateQueries('todos')
25-
queryClient.invalidateQueries('reminders')
24+
queryClient.invalidateQueries(['todos'])
25+
queryClient.invalidateQueries(['reminders'])
2626
},
2727
})
2828
```

docs/src/pages/guides/migrating-to-react-query-3.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ const {
244244
This allows for easier manipulation of the data and the page params, like, for example, removing the first page of data along with it's params:
245245

246246
```js
247-
queryClient.setQueryData('projects', data => ({
247+
queryClient.setQueryData(['projects'], data => ({
248248
pages: data.pages.slice(1),
249249
pageParams: data.pageParams.slice(1),
250250
}))
@@ -358,7 +358,7 @@ Only re-render when the `data` or `error` properties change:
358358
import { useQuery } from 'react-query'
359359

360360
function User() {
361-
const { data } = useQuery('user', fetchUser, {
361+
const { data } = useQuery(['user'], fetchUser, {
362362
notifyOnChangeProps: ['data', 'error'],
363363
})
364364
return <div>Username: {data.username}</div>
@@ -371,7 +371,7 @@ Prevent re-render when the `isStale` property changes:
371371
import { useQuery } from 'react-query'
372372

373373
function User() {
374-
const { data } = useQuery('user', fetchUser, {
374+
const { data } = useQuery(['user'], fetchUser, {
375375
notifyOnChangePropsExclusions: ['isStale'],
376376
})
377377
return <div>Username: {data.username}</div>
@@ -459,7 +459,7 @@ The `useQuery` and `useInfiniteQuery` hooks now have a `select` option to select
459459
import { useQuery } from 'react-query'
460460

461461
function User() {
462-
const { data } = useQuery('user', fetchUser, {
462+
const { data } = useQuery(['user'], fetchUser, {
463463
select: user => user.username,
464464
})
465465
return <div>Username: {data}</div>
@@ -556,10 +556,10 @@ const unsubscribe = observer.subscribe(result => {
556556
The `QueryClient.setQueryDefaults()` method can be used to set default options for specific queries:
557557

558558
```js
559-
queryClient.setQueryDefaults('posts', { queryFn: fetchPosts })
559+
queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts })
560560

561561
function Component() {
562-
const { data } = useQuery('posts')
562+
const { data } = useQuery(['posts'])
563563
}
564564
```
565565

@@ -568,10 +568,10 @@ function Component() {
568568
The `QueryClient.setMutationDefaults()` method can be used to set default options for specific mutations:
569569

570570
```js
571-
queryClient.setMutationDefaults('addPost', { mutationFn: addPost })
571+
queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost })
572572

573573
function Component() {
574-
const { mutate } = useMutation('addPost')
574+
const { mutate } = useMutation(['addPost'])
575575
}
576576
```
577577

docs/src/pages/guides/migrating-to-react-query-4.md

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ title: Migrating to React Query 4
55

66
## Breaking Changes
77

8+
### Query Keys (and Mutation Keys) need to be an Array
9+
10+
In v3, Query and Mutation Keys could be a String or an Array. Internally, React Query has always worked with Array Keys only, and we've sometimes exposed this to consumers. For example, in the `queryFn`, you would always get the key as an Array to make working with [Default Query Functions](./default-query-function) easier.
11+
12+
However, we have not followed this concept through to all apis. For example, when using the `predicate` function on [Query Filters](./filters) you would get the raw Query Key. This makes it difficult to work with such functions if you use Query Keys that are mixed Arrays and Strings. The same was true when using global callbacks.
13+
14+
To streamline all apis, we've decided to make all keys Arrays only:
15+
16+
```diff
17+
- useQuery('todos', fetchTodos)
18+
+ useQuery(['todos'], fetchTodos)
19+
```
20+
821
### Separate hydration exports have been removed
922

1023
With version [3.22.0](https://github.com/tannerlinsley/react-query/releases/tag/v3.22.0), hydration utilities moved into the react-query core. With v3, you could still use the old exports from `react-query/hydration`, but these exports have been removed with v4.

docs/src/pages/guides/mutations.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -216,34 +216,34 @@ Mutations can be persisted to storage if needed and resumed at a later point. Th
216216
const queryClient = new QueryClient()
217217

218218
// Define the "addTodo" mutation
219-
queryClient.setMutationDefaults('addTodo', {
219+
queryClient.setMutationDefaults(['addTodo'], {
220220
mutationFn: addTodo,
221221
onMutate: async (variables) => {
222222
// Cancel current queries for the todos list
223-
await queryClient.cancelQueries('todos')
223+
await queryClient.cancelQueries(['todos'])
224224

225225
// Create optimistic todo
226226
const optimisticTodo = { id: uuid(), title: variables.title }
227227

228228
// Add optimistic todo to todos list
229-
queryClient.setQueryData('todos', old => [...old, optimisticTodo])
229+
queryClient.setQueryData(['todos'], old => [...old, optimisticTodo])
230230

231231
// Return context with the optimistic todo
232232
return { optimisticTodo }
233233
},
234234
onSuccess: (result, variables, context) => {
235235
// Replace optimistic todo in the todos list with the result
236-
queryClient.setQueryData('todos', old => old.map(todo => todo.id === context.optimisticTodo.id ? result : todo))
236+
queryClient.setQueryData(['todos'], old => old.map(todo => todo.id === context.optimisticTodo.id ? result : todo))
237237
},
238238
onError: (error, variables, context) => {
239239
// Remove optimistic todo from the todos list
240-
queryClient.setQueryData('todos', old => old.filter(todo => todo.id !== context.optimisticTodo.id))
240+
queryClient.setQueryData(['todos'], old => old.filter(todo => todo.id !== context.optimisticTodo.id))
241241
},
242242
retry: 3,
243243
})
244244

245245
// Start mutation in some component:
246-
const mutation = useMutation('addTodo')
246+
const mutation = useMutation(['addTodo'])
247247
mutation.mutate({ title: 'title' })
248248

249249
// If the mutation has been paused because the device is for example offline,

docs/src/pages/guides/optimistic-updates.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,24 @@ useMutation(updateTodo, {
1616
// When mutate is called:
1717
onMutate: async newTodo => {
1818
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
19-
await queryClient.cancelQueries('todos')
19+
await queryClient.cancelQueries(['todos'])
2020

2121
// Snapshot the previous value
22-
const previousTodos = queryClient.getQueryData('todos')
22+
const previousTodos = queryClient.getQueryData(['todos'])
2323

2424
// Optimistically update to the new value
25-
queryClient.setQueryData('todos', old => [...old, newTodo])
25+
queryClient.setQueryData(['todos'], old => [...old, newTodo])
2626

2727
// Return a context object with the snapshotted value
2828
return { previousTodos }
2929
},
3030
// If the mutation fails, use the context returned from onMutate to roll back
3131
onError: (err, newTodo, context) => {
32-
queryClient.setQueryData('todos', context.previousTodos)
32+
queryClient.setQueryData(['todos'], context.previousTodos)
3333
},
3434
// Always refetch after error or success:
3535
onSettled: () => {
36-
queryClient.invalidateQueries('todos')
36+
queryClient.invalidateQueries(['todos'])
3737
},
3838
})
3939
```

docs/src/pages/guides/parallel-queries.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ When the number of parallel queries does not change, there is **no extra effort*
1212
```js
1313
function App () {
1414
// The following queries will execute in parallel
15-
const usersQuery = useQuery('users', fetchUsers)
16-
const teamsQuery = useQuery('teams', fetchTeams)
17-
const projectsQuery = useQuery('projects', fetchProjects)
15+
const usersQuery = useQuery(['users'], fetchUsers)
16+
const teamsQuery = useQuery(['teams'], fetchTeams)
17+
const projectsQuery = useQuery(['projects'], fetchProjects)
1818
...
1919
}
2020
```

0 commit comments

Comments
 (0)