---
id: manual-cache-updates
title: Manual Cache Updates
sidebar_label: Manual Cache Updates
hide_title: true
description: 'RTK Query > Usage > Manual Cache Updates: Updating and creating cached data manually'
---

&nbsp;

# Manual Cache Updates

## Overview

For most cases, in order to receive up to date data after a triggering a change in the backend, you can take advantage of cache tag invalidation to perform [automated re-fetching](./automated-refetching). This will cause a query to re-fetch its data when it has been told that a mutation has occurred which would cause its data to become out of date.

We recommend using automated re-fetching as a preference over manual cache updates in most situations.

However, there _are_ use cases when manual cache updates are necessary, such as "optimistic" or "pessimistic" updates, or modifying data as part of cache entry lifecycles.

RTK Query exports thunks for these use cases, attached to `api.utils`:

- [`updateQueryData`](../api/created-api/api-slice-utils.mdx#updatequerydata): updates an already existing cache entry
- [`upsertQueryData`](../api/created-api/api-slice-utils.mdx#upsertquerydata): creates or replaces cache entries

Since these are thunks, you can dispatch them anywhere you have access to `dispatch`.

### Updating existing cache entries

For updates of existing cache entries, use [`updateQueryData`](../api/created-api/api-slice-utils.mdx#updatequerydata).

`updateQueryData` is strictly intended to perform _updates_ to existing cache entries, not create new entries. If an `updateQueryData` thunk action is dispatched and the `endpointName` + `args` combination that does not match any existing cache entry, the provided `recipe` callback will not be called, and no `patches` or `inversePatches` will be returned.

Use cases for manual update of cache entries:

- Providing immediate feedback to the user when a mutation is attempted
- After a mutation, updating a single item in a large list of items that is already cached, rather than re-fetching the whole list
- Debouncing a large number of mutations with immediate feedback as though they are being applied, followed by a single request sent to the server to update the debounced attempts

### Creating new cache entries or replacing existing ones

To create or replace existing cache entries, use [`upsertQueryData`](../api/created-api/api-slice-utils.mdx#upsertquerydata).

`upsertQueryData` is intended to perform _replacements_ to existing cache entries or _creation_ of new ones. Since `upsertQueryData` does not have access to the previous state of the cache entry, the update may be performed only as a replacement. In comparison, `updateQueryData` allows patching of the existing cache entry, but cannot create a new one.

One example use case is [pessimistic updates](../usage/manual-cache-updates.mdx#pessimistic-updates). If the client makes an API call to create a `Post`, the backend could return its complete data including the `id`. Then we can use `upsertQueryData` to create a new cache entry for the `getPostById(id)` query, preventing an extra fetch to retrieve the item later.

## Recipes

### Optimistic Updates

When you wish to perform an update to cache data immediately after a [`mutation`](./mutations) is
triggered, you can apply an `optimistic update`. This can be a useful pattern for when you want to
give the user the impression that their changes are immediate, even while the mutation request is
still in flight.

The core concepts for an optimistic update are:

- when you start a query or mutation, `onQueryStarted` will be executed
- you manually update the cached data by dispatching `api.util.updateQueryData` within `onQueryStarted`
- then, in the case that `queryFulfilled` rejects:
  - you roll it back via the `.undo` property of the object you got back from the earlier dispatch,
    OR
  - you invalidate the cache data via `api.util.invalidateTags` to trigger a full re-fetch of the data

:::tip
Where many mutations are potentially triggered in short succession causing overlapping requests,
you may encounter race conditions if attempting to roll back patches using the `.undo` property
on failures. For these scenarios, it is often simplest and safest to invalidate the tags on error
instead, and re-fetch truly up-to-date data from the server.
:::

```ts title="Optimistic update mutation example (async await)"
// file: types.ts noEmit
export interface Post {
  id: number
  name: string
}

// file: api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: '/',
  }),
  tagTypes: ['Post'],
  endpoints: (build) => ({
    getPost: build.query<Post, number>({
      query: (id) => `post/${id}`,
      providesTags: ['Post'],
    }),
    updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
      query: ({ id, ...patch }) => ({
        url: `post/${id}`,
        method: 'PATCH',
        body: patch,
      }),
      // highlight-start
      async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
        const patchResult = dispatch(
          api.util.updateQueryData('getPost', id, (draft) => {
            Object.assign(draft, patch)
          }),
        )
        try {
          await queryFulfilled
        } catch {
          patchResult.undo()

          /**
           * Alternatively, on failure you can invalidate the corresponding cache tags
           * to trigger a re-fetch:
           * dispatch(api.util.invalidateTags(['Post']))
           */
        }
      },
      // highlight-end
    }),
  }),
})
```

or, if you prefer the slightly shorter version with `.catch`

```diff
-      async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
+      onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
        const patchResult = dispatch(
          api.util.updateQueryData('getPost', id, (draft) => {
            Object.assign(draft, patch)
          })
        )
-       try {
-         await queryFulfilled
-       } catch {
-         patchResult.undo()
-       }
+       queryFulfilled.catch(patchResult.undo)
      }
```

#### Example

[React Optimistic Updates](./examples#react-optimistic-updates)

### Pessimistic Updates

When you wish to perform an update to cache data based on the response received from the server
after a [`mutation`](./mutations) is triggered, you can apply a `pessimistic update`.
The distinction between a `pessimistic update` and an `optimistic update` is that the
`pessimistic update` will instead wait for the response from the server prior to updating
the cached data.

The core concepts for a pessimistic update are:

- when you start a query or mutation, `onQueryStarted` will be executed
- you await `queryFulfilled` to resolve to an object containing the transformed response from the
  server in the `data` property
- you manually update the cached data by dispatching `api.util.updateQueryData` within
  `onQueryStarted`, using the data in the response from the server for your draft updates
- you manually create a new cache entry by dispatching `api.util.upsertQueryData` within `onQueryStarted`,
  using the complete Post object returned by backend.

```ts title="Pessimistic update mutation example (async await)"
// file: types.ts noEmit
export interface Post {
  id: number
  name: string
}

// file: api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'

const api = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: '/',
  }),
  tagTypes: ['Post'],
  endpoints: (build) => ({
    getPost: build.query<Post, number>({
      query: (id) => `post/${id}`,
      providesTags: ['Post'],
    }),
    updatePost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
      query: ({ id, ...patch }) => ({
        url: `post/${id}`,
        method: 'PATCH',
        body: patch,
      }),
      // highlight-start
      async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
        try {
          const { data: updatedPost } = await queryFulfilled
          const patchResult = dispatch(
            api.util.updateQueryData('getPost', id, (draft) => {
              Object.assign(draft, updatedPost)
            }),
          )
        } catch {}
      },
      // highlight-end
    }),
    createPost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
      query: ({ id, ...body }) => ({
        url: `post/${id}`,
        method: 'POST',
        body,
      }),
      // highlight-start
      async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
        try {
          const { data: createdPost } = await queryFulfilled
          const patchResult = dispatch(
            api.util.upsertQueryData('getPost', id, createdPost),
          )
        } catch {}
      },
      // highlight-end
    }),
  }),
})
```

### General Updates

If you find yourself wanting to update cache data elsewhere in your application, you can do so
anywhere you have access to the `store.dispatch` method, including within React components via
the [useDispatch](https://react-redux.js.org/api/hooks#usedispatch) hook (or a typed version such
as [useAppDispatch](https://react-redux.js.org/using-react-redux/usage-with-typescript#define-typed-hooks)
for typescript users).

:::info
You should generally avoid manually updating the cache outside of the `onQueryStarted`
callback for a mutation without a good reason, as RTK Query is intended to be used by considering
your cached data as a reflection of the server-side state.
:::

```tsx no-transpile title="General manual cache update example"
import { api } from './api'
import { useAppDispatch } from './store/hooks'

function App() {
  const dispatch = useAppDispatch()

  function handleClick() {
    /**
     * This will update the cache data for the query corresponding to the `getPosts` endpoint,
     * when that endpoint is used with no argument (undefined).
     */
    const patchCollection = dispatch(
      api.util.updateQueryData('getPosts', undefined, (draftPosts) => {
        draftPosts.push({ id: 1, name: 'Teddy' })
      }),
    )
  }

  return <button onClick={handleClick}>Add post to cache</button>
}
```