State holds data shared by your application. Components subscribe to state read during rendering and update when that data changes.
Plain-object and array state returned to components and by getStore() is read-only. Opaque special values have the warning-only boundary described below. Change it through Actions, $patch(), or $reset(); Getters only read and compute values. Immer turns writes inside Actions and $patch() into immutable state updates.
Define a state() factory that returns the initial state. Prefer JSON-compatible strings, finite numbers, booleans, null, plain objects, and arrays. Special values have the limitations described below.
| Value or key | Runtime boundary |
|---|---|
| Plain objects and arrays | Nested ordinary property writes and array mutators inside Actions or function patches use the same immutable commit lifecycle. Public reads, including nested $state reads, are read-only. |
| Dynamic string keys and keys containing dots | Tracked as individual property keys, not dot-separated paths. Add or remove fields through Actions or $patch(); use an appropriate dictionary or optional-field type. |
| Own symbol keys in business state | Unsupported, including nested and non-enumerable keys. Initialization, Actions, patches or plugin restoration that introduce these keys throw before that state transition commits; fields are not silently discarded. |
Array indices, length and inherited built-ins |
Normal array reads and iteration are supported, including Symbol.iterator. Inherited built-ins are not user-defined symbol state fields. Custom string or symbol properties on arrays are outside the supported mutation model. |
Date, Map, Set, class instances, functions, Promises and browser objects |
Remain opaque rather than recursively proxied state. First exposure emits a runtime warning, deduplicated by actual object identity. Native reads remain available for compatibility, but in-place mutation is not supported and can bypass read-only guards, patches, Getter invalidation and notifications. Use plain representations or replace the entire value through an Action or $patch(). |
The opaque-value warning applies to Store, $state, Getter and Action reads, function-patch access and replacement normalization; it does not turn special values into read-only or reactive objects. Reading the same instance again, including on a later render, does not repeat the warning. In a function patch, mutating a Date can still change its internal value without an Immer patch or notification; Map/Set draft access is rejected by the default Immer configuration. Plugin restoration preserves its origin and normal commit notifications, but does not add special-object support.
Use string keys for business fields. StatePath is string[], and Mutation patches use Immer Patch[]. Rejecting business symbol keys does not disable built-in array iteration. A key such as 'user.name' is a single key, while ['user', 'name'] in a mutation path means two nested fields.
Do not mutate state through references retained from state(), patch payloads or plugin snapshots. The supported mutation boundary is the Store Action proxy or a synchronous $patch() draft, not external aliases. Native methods being callable does not imply reactive support for an object's internal slots.
As long as you have TypeScript's strict mode enabled, Pinia-React will automatically infer your state types. However, you might need to help it with initial empty arrays or nullable objects:
Alternatively, you can define the state with an interface and type the return value of state():
How you access state depends on the context:
Inside a React component or custom Hook, call useCounterStore(). Outside components, use getCounterStore() as described in using Stores outside components. The generic useStore and getStore aliases are also available.
Inside a store's own actions or getters, use the this keyword to access state and other store properties.
Calling $reset() runs the state() factory again to restore its defaults, even if the Store was initialized from a server snapshot. It does not restore that snapshot.
$patchWhile actions are the recommended way to change state, the $patch method is useful for batching multiple state mutations together.
The $patch method accepts a function or a shallow partial state object. This function receives a draft version of the state, powered by Immer. You can safely "mutate" this draft object, and Immer will produce a new immutable state for you.
Object patches shallowly replace top-level fields in one commit:
A nested object supplied in a patch must contain its complete replacement value. Both patch forms emit no mutation when state is unchanged. Keep function recipes synchronous and never retain their Immer draft.
You cannot directly replace the entire $state object. The following warns and also throws in strict mode:
Use Object.assign() inside $patch() to overwrite fields. It does not remove existing fields omitted from the new object:
You can listen for state changes using the store's $subscribe() method. The callback receives the new state and the previous state as arguments. It returns a function to stop the subscription.
The optional third subscription argument is the committed event. Existing callbacks with two parameters remain valid:
Getter-only invalidation from another store updates dependent components without calling state subscriptions. The mutation event belongs to the store whose state actually changed.
For automatic-tracking diagnosis, use onStoreDebug rather than logging a complete MutationEvent. It reports a diagnostic ID per real commit, the original Store, metadata without Action arguments and patch operations/paths without values. Action writes before and after await, nested Actions, patches, resets and plugin restores keep the existing Mutation lifecycle and origin; failed Actions do not undo already committed writes.
notify means an actual matched Store update request, not every subscriber callback and not a React commit. Cross-Store Getter invalidation retains the source Mutation description and does not create a target business Mutation. Diagnostic callbacks run after cache invalidation and business dispatch; they cannot be used as middleware to alter that commit. Read the Getter page for committed dependencies, privacy, production disabling and current one-hop cross-Store limits.