Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/unify dehydrate options #5131

Merged
merged 12 commits into from
Apr 1, 2023
11 changes: 10 additions & 1 deletion docs/react/guides/migrating-to-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@ However, refetching all pages might lead to UI inconsistencies. Also, this optio

The v5 includes a new `maxPages` option for infinite queries to limit the number of pages to store in the query data and to refetch. This new feature handles the use cases initially identified for the `refetchPage` page feature without the related issues.

### New hydration API

The options you can pass to dehydrate have been simplified. Queries and Mutations are always dehydrated (according to the default function implementation). To change this behaviour, you can implement `shouldDehydrateQuery` or `shouldDehydrateMutation`.

```diff
- dehydrateMutations?: boolean
- dehydrateQueries?: boolean
```

### Infinite queries now need a `defaultPageParam`

Previously, we've passed `undefined` to the `queryFn` as `pageParam`, and you could assign a default value to the `pageParam` parameter in the `queryFn` function signature. This had the drawback of storing `undefined` in the `queryCache`, which is not serializable.
Expand Down Expand Up @@ -410,7 +419,7 @@ We have a new, simplified way to perform optimistic updates by leveraging the re

Here, we are only changing how the UI looks when the mutation is running instead of writing data directly to the cache. This works best if we only have one place where we need to show the optimistic update. For more details, have a look at the [optimistic updates documentation](../guides/optimistic-updates.md).

### Eternal list: scalable infinite query with new maxPages option
### Limited, Infinite Queries with new maxPages option

Infinite queries are great when infinite scroll or pagination are needed.
However, the more pages you fetch, the more memory you consume, and this also slows down the query refetching process as all the pages are sequentially refetched.
Expand Down
31 changes: 14 additions & 17 deletions docs/react/reference/hydration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { dehydrate } from '@tanstack/react-query'

const dehydratedState = dehydrate(queryClient, {
shouldDehydrateQuery,
shouldDehydrateMutation,
})
```

Expand All @@ -22,24 +23,20 @@ const dehydratedState = dehydrate(queryClient, {
- The `queryClient` that should be dehydrated
- `options: DehydrateOptions`
- Optional
- `dehydrateMutations: boolean`
- Optional
- Whether or not to dehydrate mutations.
- `dehydrateQueries: boolean`
- Optional
- Whether or not to dehydrate queries.
- `shouldDehydrateMutation: (mutation: Mutation) => boolean`
- Optional
- This function is called for each mutation in the cache
- Return `true` to include this mutation in dehydration, or `false` otherwise
- The default version only includes paused mutations
- If you would like to extend the function while retaining the previous behavior, import and execute `defaultShouldDehydrateMutation` as part of the return statement
- `shouldDehydrateQuery: (query: Query) => boolean`
- Whether to dehydrate mutations.
- The function is called for each mutation in the cache
- Return `true` to include this mutation in dehydration, or `false` otherwise
- Defaults to only including paused mutations
- If you would like to extend the function while retaining the default behavior, import and execute `defaultShouldDehydrateMutation` as part of the return statement
- `shouldDehydrateQuery: boolean | (query: Query) => boolean`
- Optional
- This function is called for each query in the cache
- Return `true` to include this query in dehydration, or `false` otherwise
- The default version only includes successful queries, do `shouldDehydrateQuery: () => true` to include all queries
- If you would like to extend the function while retaining the previous behavior, import and execute `defaultShouldDehydrateQuery` as part of the return statement
- Whether to dehydrate queries.
- The function, it is called for each query in the cache
- Return `true` to include this query in dehydration, or `false` otherwise
- Defaults to only including successful queries
- If you would like to extend the function while retaining the default behavior, import and execute `defaultShouldDehydrateQuery` as part of the return statement

**Returns**

Expand All @@ -50,11 +47,11 @@ const dehydratedState = dehydrate(queryClient, {

### limitations

Some storage systems (such as browser [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)) require values to be JSON serializable. If you need to dehydrate values that are not automatically serializable to JSON (like `Error` or `undefined`), you have to serialize them for yourself. Since only successful queries are included per default, to also include `Errors`, you have to provide `shouldDehydrateQuery`, e.g.:
Some storage systems (such as browser [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)) require values to be JSON serializable. If you need to dehydrate values that are not automatically serializable to JSON (like `Error` or `undefined`), you have to serialize them for yourself. Since only successful queries are included per default, to also include `Errors`, you have to provide `dehydrateQuery`, e.g.:
TkDodo marked this conversation as resolved.
Show resolved Hide resolved

```tsx
// server
const state = dehydrate(client, { shouldDehydrateQuery: () => true }) // to also include Errors
const state = dehydrate(client, { dehydrateQuery: () => true }) // to also include Errors
TkDodo marked this conversation as resolved.
Show resolved Hide resolved
const serializedState = mySerialize(state) // transform Error instances to objects

// client
Expand Down
55 changes: 18 additions & 37 deletions packages/query-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import type { Mutation, MutationState } from './mutation'
// TYPES

export interface DehydrateOptions {
dehydrateMutations?: boolean
dehydrateQueries?: boolean
shouldDehydrateMutation?: ShouldDehydrateMutationFunction
shouldDehydrateQuery?: ShouldDehydrateQueryFunction
shouldDehydrateMutation?: (mutation: Mutation) => boolean
shouldDehydrateQuery?: (query: Query) => boolean
}

export interface HydrateOptions {
Expand All @@ -40,10 +38,6 @@ export interface DehydratedState {
queries: DehydratedQuery[]
}

export type ShouldDehydrateQueryFunction = (query: Query) => boolean

export type ShouldDehydrateMutationFunction = (mutation: Mutation) => boolean

// FUNCTIONS

function dehydrateMutation(mutation: Mutation): DehydratedMutation {
Expand Down Expand Up @@ -77,36 +71,23 @@ export function dehydrate(
client: QueryClient,
options: DehydrateOptions = {},
): DehydratedState {
const mutations: DehydratedMutation[] = []
const queries: DehydratedQuery[] = []

if (options.dehydrateMutations !== false) {
const shouldDehydrateMutation =
options.shouldDehydrateMutation || defaultShouldDehydrateMutation

client
.getMutationCache()
.getAll()
.forEach((mutation) => {
if (shouldDehydrateMutation(mutation)) {
mutations.push(dehydrateMutation(mutation))
}
})
}
const filterMutation =
options.shouldDehydrateMutation ?? defaultShouldDehydrateMutation

const mutations = client
.getMutationCache()
.getAll()
.flatMap((mutation) =>
filterMutation(mutation) ? [dehydrateMutation(mutation)] : [],
)

if (options.dehydrateQueries !== false) {
const shouldDehydrateQuery =
options.shouldDehydrateQuery || defaultShouldDehydrateQuery

client
.getQueryCache()
.getAll()
.forEach((query) => {
if (shouldDehydrateQuery(query)) {
queries.push(dehydrateQuery(query))
}
})
}
const filterQuery =
options.shouldDehydrateQuery ?? defaultShouldDehydrateQuery

const queries = client
.getQueryCache()
.getAll()
.flatMap((query) => (filterQuery(query) ? [dehydrateQuery(query)] : []))

return { mutations, queries }
}
Expand Down
4 changes: 1 addition & 3 deletions packages/query-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export { isCancelledError } from './retryer'
export {
dehydrate,
hydrate,
defaultShouldDehydrateMutation,
defaultShouldDehydrateQuery,
defaultShouldDehydrateMutation,
} from './hydration'

// Types
Expand All @@ -35,6 +35,4 @@ export type {
DehydrateOptions,
DehydratedState,
HydrateOptions,
ShouldDehydrateMutationFunction,
ShouldDehydrateQueryFunction,
} from './hydration'
10 changes: 6 additions & 4 deletions packages/query-core/src/tests/hydration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe('dehydration and rehydration', () => {
queryFn: () => fetchData('string'),
})

const dehydrated = dehydrate(queryClient, { dehydrateQueries: false })
const dehydrated = dehydrate(queryClient, { dehydrateQuery: () => false })

expect(dehydrated.queries.length).toBe(0)

Expand Down Expand Up @@ -244,7 +244,7 @@ describe('dehydration and rehydration', () => {
consoleMock.mockRestore()
})

test('should filter queries via shouldDehydrateQuery', async () => {
test('should filter queries via dehydrateQuery', async () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
await queryClient.prefetchQuery({
Expand All @@ -256,7 +256,7 @@ describe('dehydration and rehydration', () => {
queryFn: () => fetchData(1),
})
const dehydrated = dehydrate(queryClient, {
shouldDehydrateQuery: (query) => query.queryKey[0] !== 'string',
dehydrateQuery: (query) => query.queryKey[0] !== 'string',
})

// This is testing implementation details that can change and are not
Expand Down Expand Up @@ -446,7 +446,9 @@ describe('dehydration and rehydration', () => {
).catch(() => undefined)

await sleep(1)
const dehydrated = dehydrate(queryClient, { dehydrateMutations: false })
const dehydrated = dehydrate(queryClient, {
dehydrateMutation: () => false,
})

expect(dehydrated.mutations.length).toBe(0)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('persistQueryClientSubscribe', () => {
const unsubscribe = persistQueryClientSubscribe({
queryClient,
persister,
dehydrateOptions: { shouldDehydrateMutation: () => true },
dehydrateOptions: { dehydrateMutation: () => true },
})

queryClient.getMutationCache().build(queryClient, {
Expand Down