Skip to content

2920 mutation cache duration #2963

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

Merged
merged 12 commits into from
Nov 17, 2021
Merged
27 changes: 27 additions & 0 deletions docs/src/pages/guides/migrating-to-react-query-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ For the same reason, those have also been combined:

This flag defaults to `active` because `refetchActive` defaulted to `true`. This means we also need a way to tell `invalidateQueries` to not refetch at all, which is why a fourth option (`none`) is also allowed here.

### Streamlined NotifyEvents

Subscribing manually to the `QueryCache` has always given you a `QueryCacheNotifyEvent`, but this was not true for the `MutationCache`. We have streamlined the behavior and also adapted event names accordingly.

#### QueryCacheNotifyEvent

```diff
- type: 'queryAdded'
+ type: 'added'
- type: 'queryRemoved'
+ type: 'removed'
- type: 'queryUpdated'
+ type: 'updated'
```

#### MutationCacheNotifyEvent

The `MutationCacheNotifyEvent` uses the same types as the `QueryCacheNotifyEvent`.

> Note: This is only relevant if you manually subscribe to the caches via `queryCache.subscribe` or `mutationCache.subscribe`

### The `src/react` directory was renamed to `src/reactjs`

Previously, react-query had a directory named `react` which imported from the `react` module. This could cause problems with some Jest configurations, resulting in errors when running tests like:
Expand All @@ -110,3 +131,9 @@ If you were importing anything from `'react-query/react'` directly in your proje
- import { QueryClientProvider } from 'react-query/react';
+ import { QueryClientProvider } from 'react-query/reactjs';
```

## New Features 🚀

### Mutation Cache Garbage Collection

Mutations can now also be garbage collected automatically, just like queries. The default `cacheTime` for mutations is also set to 5 minutes.
6 changes: 3 additions & 3 deletions docs/src/pages/reference/MutationCache.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@ const mutations = mutationCache.getAll()
The `subscribe` method can be used to subscribe to the mutation cache as a whole and be informed of safe/known updates to the cache like mutation states changing or mutations being updated, added or removed.

```js
const callback = mutation => {
console.log(mutation)
const callback = event => {
console.log(event.type, event.mutation)
}

const unsubscribe = mutationCache.subscribe(callback)
```

**Options**

- `callback: (mutation?: Mutation) => void`
- `callback: (mutation?: MutationCacheNotifyEvent) => void`
- This function will be called with the mutation cache any time it is updated.

**Returns**
Expand Down
6 changes: 5 additions & 1 deletion docs/src/pages/reference/useMutation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ const {
reset,
status,
} = useMutation(mutationFn, {
cacheTime,
mutationKey,
onError,
onMutate,
onSettled,
onSuccess,
useErrorBoundary,
meta,
meta
})

mutate(variables, {
Expand All @@ -39,6 +40,9 @@ mutate(variables, {
- **Required**
- A function that performs an asynchronous task and returns a promise.
- `variables` is an object that `mutate` will pass to your `mutationFn`
- `cacheTime: number | Infinity`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mention a default here is Infinity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default is 5 minutes, like it is for queries.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh got it, thanks!

- The time in milliseconds that unused/inactive cache data remains in memory. When a mutation's cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different cache times are specified, the longest one will be used.
- If set to `Infinity`, will disable garbage collection
- `mutationKey: string`
- Optional
- A mutation key can be set to inherit defaults set with `queryClient.setMutationDefaults` or to identify the mutation in the devtools.
Expand Down
12 changes: 6 additions & 6 deletions src/broadcastQueryClient-experimental/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@ export function broadcastQueryClient({
} = queryEvent

if (
queryEvent.type === 'queryUpdated' &&
queryEvent.type === 'updated' &&
queryEvent.action?.type === 'success'
) {
channel.postMessage({
type: 'queryUpdated',
type: 'updated',
queryHash,
queryKey,
state,
})
}

if (queryEvent.type === 'queryRemoved') {
if (queryEvent.type === 'removed') {
channel.postMessage({
type: 'queryRemoved',
type: 'removed',
queryHash,
queryKey,
})
Expand All @@ -61,7 +61,7 @@ export function broadcastQueryClient({
tx(() => {
const { type, queryHash, queryKey, state } = action

if (type === 'queryUpdated') {
if (type === 'updated') {
const query = queryCache.get(queryHash)

if (query) {
Expand All @@ -77,7 +77,7 @@ export function broadcastQueryClient({
},
state
)
} else if (type === 'queryRemoved') {
} else if (type === 'removed') {
const query = queryCache.get(queryHash)

if (query) {
Expand Down
45 changes: 43 additions & 2 deletions src/core/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MutationCache } from './mutationCache'
import type { MutationObserver } from './mutationObserver'
import { getLogger } from './logger'
import { notifyManager } from './notifyManager'
import { Removable } from './removable'
import { Retryer } from './retryer'
import { noop } from './utils'

Expand Down Expand Up @@ -81,7 +82,7 @@ export class Mutation<
TError = unknown,
TVariables = void,
TContext = unknown
> {
> extends Removable {
state: MutationState<TData, TError, TVariables, TContext>
options: MutationOptions<TData, TError, TVariables, TContext>
mutationId: number
Expand All @@ -92,6 +93,8 @@ export class Mutation<
private retryer?: Retryer<TData, TError>

constructor(config: MutationConfig<TData, TError, TVariables, TContext>) {
super()

this.options = {
...config.defaultOptions,
...config.options,
Expand All @@ -101,6 +104,9 @@ export class Mutation<
this.observers = []
this.state = config.state || getDefaultState()
this.meta = config.meta

this.updateCacheTime(this.options.cacheTime)
this.scheduleGc()
}

setState(state: MutationState<TData, TError, TVariables, TContext>): void {
Expand All @@ -110,11 +116,42 @@ export class Mutation<
addObserver(observer: MutationObserver<any, any, any, any>): void {
if (this.observers.indexOf(observer) === -1) {
this.observers.push(observer)

// Stop the mutation from being garbage collected
this.clearGcTimeout()

this.mutationCache.notify({
type: 'observerAdded',
mutation: this,
observer,
})
}
}

removeObserver(observer: MutationObserver<any, any, any, any>): void {
this.observers = this.observers.filter(x => x !== observer)

if (this.cacheTime) {
this.scheduleGc()
} else {
this.mutationCache.remove(this)
}

this.mutationCache.notify({
type: 'observerRemoved',
mutation: this,
observer,
})
}

protected optionalRemove() {
if (!this.observers.length) {
if (this.state.status === 'loading') {
this.scheduleGc()
} else {
this.mutationCache.remove(this)
}
}
}

cancel(): Promise<void> {
Expand Down Expand Up @@ -252,7 +289,11 @@ export class Mutation<
this.observers.forEach(observer => {
observer.onMutationUpdate(action)
})
this.mutationCache.notify(this)
this.mutationCache.notify({
mutation: this,
type: 'updated',
action,
})
})
}
}
Expand Down
57 changes: 41 additions & 16 deletions src/core/mutationCache.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { MutationObserver } from './mutationObserver'
import type { MutationOptions } from './types'
import type { QueryClient } from './queryClient'
import { notifyManager } from './notifyManager'
import { Mutation, MutationState } from './mutation'
import { Action, Mutation, MutationState } from './mutation'
import { matchMutation, MutationFilters, noop } from './utils'
import { Subscribable } from './subscribable'
import { Notifiable } from './notifiable'

// TYPES

Expand All @@ -12,21 +13,53 @@ interface MutationCacheConfig {
error: unknown,
variables: unknown,
context: unknown,
mutation: Mutation<unknown, unknown, unknown, unknown>
mutation: Mutation<unknown, unknown, unknown>
) => void
onSuccess?: (
data: unknown,
variables: unknown,
context: unknown,
mutation: Mutation<unknown, unknown, unknown, unknown>
mutation: Mutation<unknown, unknown, unknown>
) => void
}

type MutationCacheListener = (mutation?: Mutation) => void
interface NotifyEventMutationAdded {
type: 'added'
mutation: Mutation<any, any, any, any>
}
interface NotifyEventMutationRemoved {
type: 'removed'
mutation: Mutation<any, any, any, any>
}

interface NotifyEventMutationObserverAdded {
type: 'observerAdded'
mutation: Mutation<any, any, any, any>
observer: MutationObserver<any, any, any>
}

interface NotifyEventMutationObserverRemoved {
type: 'observerRemoved'
mutation: Mutation<any, any, any, any>
observer: MutationObserver<any, any, any>
}

interface NotifyEventMutationUpdated {
type: 'updated'
mutation: Mutation<any, any, any, any>
action: Action<any, any, any, any>
}

type MutationCacheNotifyEvent =
| NotifyEventMutationAdded
| NotifyEventMutationRemoved
| NotifyEventMutationObserverAdded
| NotifyEventMutationObserverRemoved
| NotifyEventMutationUpdated

// CLASS

export class MutationCache extends Subscribable<MutationCacheListener> {
export class MutationCache extends Notifiable<MutationCacheNotifyEvent> {
config: MutationCacheConfig

private mutations: Mutation<any, any, any, any>[]
Expand Down Expand Up @@ -62,13 +95,13 @@ export class MutationCache extends Subscribable<MutationCacheListener> {

add(mutation: Mutation<any, any, any, any>): void {
this.mutations.push(mutation)
this.notify(mutation)
this.notify({ type: 'added', mutation })
}

remove(mutation: Mutation<any, any, any, any>): void {
this.mutations = this.mutations.filter(x => x !== mutation)
mutation.cancel()
this.notify(mutation)
this.notify({ type: 'removed', mutation })
}

clear(): void {
Expand Down Expand Up @@ -97,14 +130,6 @@ export class MutationCache extends Subscribable<MutationCacheListener> {
return this.mutations.filter(mutation => matchMutation(filters, mutation))
}

notify(mutation?: Mutation<any, any, any, any>) {
notifyManager.batch(() => {
this.listeners.forEach(listener => {
listener(mutation)
})
})
}

onFocus(): void {
this.resumePausedMutations()
}
Expand Down
12 changes: 12 additions & 0 deletions src/core/notifiable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Subscribable } from './subscribable'
import { notifyManager } from '../core/notifyManager'

export class Notifiable<TEvent> extends Subscribable<(event: TEvent) => void> {
notify(event: TEvent) {
notifyManager.batch(() => {
this.listeners.forEach(listener => {
listener(event)
})
})
}
}
Loading