Skip to content
Zap Studio
store
Esc
navigateopen⌘Jpreview
On this page

set

set only takes an updater function, and the result is always shallow-merged into state.

set is the setter passed into actionsFactory when you create a store. It only takes one form: an updater function.

set((prev) => partialOrFullState);

There is no set({ ... }) shortcut. This is deliberate — see Why No Bare-Object Form below.

Shallow Merge

Whatever the updater returns is shallow-merged into the current state — the same way Object.assign or object-spread would combine it with the previous state. Keys you don’t mention are left untouched.

const user = createStore({ name: "Ada", age: 30 }, (set) => ({
  haveBirthday: () => set((s) => ({ age: s.age + 1 })), // only `age` changes
}));

user.get().haveBirthday();
user.getState(); // { name: "Ada", age: 31 }

Reading the Previous State

The updater always receives the current state as its only argument — no separate get call needed for a simple read-then-write update:

const counter = createStore({ count: 0 }, (set) => ({
  increment: () => set((s) => ({ count: s.count + 1 })),
}));

For an action that needs to read state without also writing it in the same call, use the get parameter from actionsFactory instead:

const counter = createStore({ count: 5 }, (set, get) => ({
  double: () => set(() => ({ count: get().count * 2 })),
}));

Why No Bare-Object Form

Zustand’s setState accepts both a partial object and an updater function, and merges either way. That flexibility hides a real ambiguity: does set({ count: 1 }) merge count into the existing state, or replace the whole state with { count: 1 }? Zustand answers “merge,” but that answer is not visible at the call site — you have to know the library’s convention.

store removes the ambiguity by only accepting the updater form. It stays a one-liner for simple updates (set(() => ({ count: 1 }))), and every call site is unambiguous about being a merge.

Nested State Is Not Deep-Merged

Shallow merge means only the top level of state is merged — a nested object is replaced whole, not merged field by field:

const form = createStore({ user: { name: "Ada", age: 30 } }, (set) => ({
  // Wrong: this replaces `user` entirely, dropping `age`.
  renameWrong: (name: string) => set(() => ({ user: { name } })),

  // Right: spread the previous nested object first.
  rename: (name: string) => set((s) => ({ user: { ...s.user, name } })),
}));

This is the same rule Zustand and most state containers follow — it is called out here because store removing the bare-object overload does not change it.

See Also

  • Getting Started — creating a store and adding actions
  • Persist — what happens to state written through set when persist is enabled

Last updated on September 21, 2026

Was this page helpful?