Optimistic UI with Immer
Purpose and Scope
This page explains the repository’s Optimistic UI with Immer example: a small application pattern where the user interface updates immediately, before the server has confirmed the change, and then SWR revalidates against the API to converge on the authoritative result. In SWR terminology, this is local mutation: the cache is written first so React can render the expected next state without waiting for a network round trip. The example’s official description frames the goal as using SWR and Immer together to mutate cached data immediately and then trigger API revalidation.
The important reader problem is not simply how to call a helper; it is how to reason about trust boundaries. The browser can show an optimistic state because SWR’s cache is local and reactive, but the remote API remains the source of truth. Immer fits into that workflow as an ergonomic way to express immutable updates: instead of manually cloning every nested object or array, the application writes update logic in a draft-like style and produces a new cached value for SWR to publish. SWR then revalidates so any server-side validation, generated fields, ordering, or rejection is reflected back into the UI.
Sources: README.md
Relevant Source Files
README.md— Defines SWR as a React Hooks data-fetching library, explains the stale-while-revalidate model, shows the primaryuseSWRreturn shape, and lists local mutation as an Optimistic UI capability.e2e/site/README.md— Documents the repository’s Next.js development-server conventions, includingnpm run dev,yarn dev,pnpm dev, browser access athttp://localhost:3000, and the mapping frompages/apifiles to/api/*routes.src/_internal/utils/with-middleware.ts— Shows how SWR wraps hooks with middleware by normalizing hook arguments and appending middleware to theconfig.usearray before calling the underlying hook.
Conceptual Model
SWR stands for stale-while-revalidate: it first returns cached data, then sends a request, and finally updates the UI with fresh data when the request resolves. That model is the foundation for optimistic UI because cached data is already allowed to be shown before the newest network result arrives. In the normal read path, the cache may contain stale data from a previous request. In the optimistic write path, the cache contains a deliberate prediction of what the server will return after a user action. The mechanics differ, but the rendering contract is the same: React components subscribe to SWR state and re-render when cache-backed data changes.
The root README’s quick start shows the basic shape that optimistic examples build on: useSWR accepts a key and a fetcher, then returns data, error, and isLoading. The key identifies the request, often by URL, and the fetcher asynchronously returns the data for that key. An optimistic UI example uses the same key identity to decide which cache entry should be updated. If the page displays a list under one API URL, the optimistic update must write the predicted next list to that same cache entry so all components reading that key see the immediate result.
Sources: README.md
Running the Example
The official Optimistic UI with Immer example can be downloaded from the repository’s examples tree and run as a standalone app. The documented flow is to extract the examples/optimistic-ui-immer folder from the main branch archive, enter the folder, install dependencies, and start the development server. The commands are intentionally the same style as other SWR examples, so readers can compare this recipe with the non-Immer optimistic UI example or with basic data-fetching examples without learning a separate project layout.
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/optimistic-ui-immer
cd optimistic-ui-immer
yarn
yarn dev
# or
npm install
npm run devFor repository-local development, the E2E site README confirms the standard Next.js development-server pattern: run a dev command, then open http://localhost:3000 in a browser. It also documents that files under pages/api are treated as API routes and are mapped to /api/*. That matters for optimistic UI examples because the UI usually needs both a page that renders SWR state and an API route that accepts or returns the remote data. When adapting the example, keep the client-visible cache key aligned with the API route that the fetcher and mutation request use.
Sources: e2e/site/README.md
Execution Flow
A practical optimistic-with-Immer flow starts when a component has already loaded data through useSWR. At that point, the screen is rendering data associated with a stable key. When the user submits a change, the event handler constructs the expected next value with Immer and writes it into SWR’s cache through mutation. The write is synchronous from the user’s perspective: the list item appears, a checkbox toggles, or a count changes immediately. The network request can then proceed in parallel, allowing the UI to feel responsive even when the API is slow.
After the optimistic cache write, the example pattern triggers revalidation with the API. Revalidation is the step that protects the application from becoming a purely client-side illusion. If the server returns the same value, the UI remains stable and the user experiences the action as instant. If the server returns a different canonical value, SWR publishes that fresh data and the UI corrects itself. If the request fails, production code should decide whether to roll back, keep a pending marker, show an error, or request user intervention. The supplied example description emphasizes immediate cached mutation followed by API revalidation, which is the key sequencing to preserve.
Immer is especially useful when the cached value has nested structure. Without Immer, optimistic code often spreads arrays and objects at several levels, which can make the intended domain change harder to see. With Immer, the handler can express the change as if it were mutating a draft, while the produced value remains suitable for React and SWR cache updates. The important constraint is that the result must still represent the full value expected for the cache key. SWR subscribers do not know that Immer was used; they only observe that the cached data for their key changed.
Sources: README.md
SWR API Components Used by the Pattern
The primary public component in this pattern is useSWR. It binds a React component to a cache key and a fetcher and exposes render state. The README’s quick start shows how components branch on error and isLoading, then render data once available. Optimistic UI adds a write path to that read path: the same component, or a child event handler, updates the relevant cache entry before revalidation. This keeps the rendering model simple because the component still reads from SWR instead of duplicating remote state into separate React state.
The fetcher remains transport agnostic. The README explicitly notes that the fetcher can be any asynchronous function and that developers can use their preferred data-fetching library. For the Immer example, that means the optimistic recipe is not tied to fetch, REST, or a particular API implementation. The essential contract is that the fetcher returns the remote representation for the key and that the mutation request changes the same resource. When those contracts drift, the UI can optimistically update one value while revalidation replaces it with unrelated data.
SWR middleware is not required to run the official Optimistic UI with Immer example, but the internal withMiddleware helper shows how extension points are composed when a project wants cross-cutting behavior around hooks. The helper normalizes the hook arguments, reads any existing config.use middleware array, appends the new middleware, and calls the underlying SWR hook with the merged configuration. This is useful context for advanced teams that want logging, instrumentation, or policy around optimistic mutations while preserving the ordinary useSWR call signature.
Sources: src/_internal/utils/with-middleware.ts
Implementation Notes and Guardrails
Keep optimistic updates scoped to the same cache key that renders the affected data. In SWR’s quick-start model, the key is the unique identifier of the request, normally the API URL. If a page renders /api/items, an optimistic insertion into a different key will not update the visible list. Conversely, writing a broad key for a narrow change can make unrelated UI move unexpectedly. The safest implementation style is to define the fetch key close to the hook and reuse that same value when triggering the optimistic mutation and subsequent revalidation.
Model loading and error states deliberately. The README describes data as undefined and isLoading as true before the fetcher finishes, with error set according to the fetch result. An optimistic action may happen after initial data is loaded, while a revalidation is in flight, or after an error has occurred. The UI should communicate pending work without discarding the optimistic value too early. For example, an item can be rendered immediately with a pending style while the API request completes, then the revalidated SWR data removes or confirms that marker.
When adapting this recipe to a Next.js app, remember that local API routes and client pages are separate concerns. The E2E site README describes how pages/api maps to /api/*, which is the convention a small example can use for the server side of the mutation. The page component owns the SWR hook and event handler; the API route owns validation, persistence, and the canonical response. Optimistic UI is strongest when those two layers agree on data shape, because the Immer-produced predicted value can closely match the server result.
Sources: README.md, e2e/site/README.md
Next Steps
After running the example, compare it with the plain Optimistic UI recipe to decide whether Immer adds enough clarity for your data shape. If your update is a shallow replacement, direct immutable JavaScript may be sufficient. If your update edits nested arrays, maps, or objects, Immer can make the optimistic branch easier to review. Next, read the mutation API reference for rollback and revalidation options, then review the cache and provider documentation so you understand which components will observe each optimistic write.
Related pages: mutation-concepts, api-mutate, example-optimistic-ui, cache-and-provider, api-middleware