Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iDevRanjan/nexus-store/llms.txt

Use this file to discover all available pages before exploring further.

Working with deeply nested state in JavaScript normally means chaining spread operators on every level just to satisfy immutability requirements. Immer eliminates that ceremony by letting you write direct mutations against a draft proxy — it then produces a new immutable state behind the scenes. NexusStore supports Immer through two distinct patterns: a per-call produce approach that requires no changes to your store, and an immer() store wrapper that makes every setState and setMultiple call automatically Immer-aware.

Installation

Install Immer as a peer dependency before using either pattern.
npm install immer

Why Immer?

Without Immer, updating a nested object requires a full spread chain at every level to avoid mutating the existing state reference:
Without Immer — spread chains get verbose fast
store.setState(
  (keys) => keys.cart,
  (currentCart) => ({
    ...currentCart,
    items: [...currentCart.items, "Kiwi"],
  }),
);
With Immer you write the mutation directly. Immer intercepts it on a draft proxy and hands NexusStore a fresh immutable copy:
With Immer — direct mutation, same result
store.setState(
  (keys) => keys.cart,
  (draft) => {
    draft.items.push("Kiwi");
  },
);
Both snippets produce identical state — the Immer version just reads the way you think about the update.

Two Integration Patterns

When you are working with a plain createStore store and only need Immer for a specific update, import produce directly from the immer package and wrap your updater function.
Do not pass a baseState as the first argument to produce. Calling produce((draft) => { ... }) with only a recipe function returns a curried producer — NexusStore passes the current slice of state to it automatically. Providing a base state yourself will bypass NexusStore’s state management.
The AuthComponent in the demo app uses this pattern. The store (authStore) is a plain createStore store with no Immer wrapping, so produce is imported at the component level and used inline:
src/components/AuthComponent.jsx
import { useAuthStore } from "../store/hooks";
import { authStore } from "../store/store";
import { produce } from "immer";

export default function AuthComponent() {
  console.log("%c AuthComponent Rendered", "color: orange");
  const auth = useAuthStore((state) => state.auth);

  return (
    <fieldset>
      <legend>Auth</legend>
      <p>{auth.isLoggedin ? "Logged In" : "Logged Out"}</p>
      <button
        onClick={() =>
          authStore.setState(
            (keys) => keys.auth,
            produce((draft) => {
              draft.isLoggedin = !draft.isLoggedin;
            }),
          )
        }
      >
        Toggle Auth
      </button>
    </fieldset>
  );
}
This approach works well for one-off updates. Its downside is that you must remember to import and wrap produce at every call site.
immer() is a store factory wrapper — call it once when you create the store, not inside a React component or event handler. Calling it per render would create a new store object on every render cycle and discard all stored state.

Spread Operators vs. Immer Draft — Side-by-Side

The table below shows the same operations written both ways so you can see the readability gains at a glance.
OperationWithout ImmerWith Immer
Push to array[...curr.items, "Kiwi"]draft.items.push("Kiwi")
Pop from arraycurr.items.slice(0, -1)draft.items.pop()
Toggle nested boolean{ ...curr, isLoggedin: !curr.isLoggedin }draft.isLoggedin = !draft.isLoggedin
Update nested string{ ...curr, mode: "light" }draft.mode = "light"

Debugging Draft Values

Logging an Immer draft proxy directly produces an unreadable Proxy object in the console. Import current from immer to get a plain snapshot of the draft at that moment:
Debugging a draft with current()
import { current } from "immer";

store.setMultiple((draft) => {
  console.log(current(draft)); // plain JS object — readable in DevTools
  draft.auth.isLoggedin = false;
  draft.theme.mode = "light";
});
current(draft) is a zero-cost debug helper — it has no effect on the resulting state update. Remove it before shipping to production.

Build docs developers (and LLMs) love