Skip to main content
Where state lives shapes everything else. The framework: put state at the lowest layer that satisfies the requirement.

Decision tree

1

Should everyone see this state? (Multi-user, persistent.)

Yes: Convex (mutation writes to DB; queries read).
2

Should it survive a page refresh? (Single-user, persistent.)

Yes: localStorage (or per-user Convex row if multi-device).
3

Should it be shareable via URL? (Filters, tabs, search.)

Yes: URL query parameters.
4

Multi-component, ephemeral?

Yes: Zustand (or React context for tree-scoped).
5

Single component, ephemeral?

Yes: useState / useReducer.

Examples

Common mistakes

useQuery is the cache. Don’t copy results into a Zustand store; you’ll fight with stale data.
A user sets filters, refreshes, all gone. Use URL query params instead.
Each keystroke = a mutation = wasted credits and DB churn. Use local state; persist on submit.

Tips

For collaborative editing, every keystroke goes to Convex. That’s not waste — that’s the feature. Reactive sync makes co-editing automatic.
Use useReducer for complex local state. When useState calls multiply (5+ in a single component), a reducer simplifies.

Convex queries

Server state.

Optimistic updates

Pattern for instant client feedback.

Component patterns

Where state lives in your component tree.
Last modified on April 18, 2026