Documentation
Docs
Introduction
Getting Started
Tutorial: build an app
Core Concepts
Routing
Server-Side Rendering
Mimir, state management
Pages & Layouts
API Routes
Styling & Theming
Building & Deploying
API Reference
Guides
Recipes
FAQ (use cases)
Mimir pitfalls
The mistakes that actually bite, each with the symptom and the fix. If a Mimir-related bug is confusing you, it is almost certainly one of these.
1. Mutating a value in place
Symptom: you set an array/object but nothing re-renders.
Cause: Mimir compares the new value to the old with Object.is. If you mutate the existing object and
return the same reference, it looks unchanged, so no notification.
Fix: always produce a new value.
2. Duplicate or colliding keys
Symptom: two unrelated pieces of state interfere; setting one changes the other.
Cause: two atoms share a key. The key is the identity, same key = same store slot.
Fix: make keys globally unique and namespaced ("cart:items", "ui:sidebar-open"). Treat keys like
database column names.
3. Renaming a key
Symptom: persisted state or server-seeded state "disappears" after a refactor.
Cause: the value is stored under the old key (in IndexedDB) and seeded under the old key (in __atoms).
Renaming orphans both.
Fix: keep keys stable. If you must rename, migrate persisted data, or accept that existing users reset once.
4. Non-serializable values
Symptom: SSR seeding or persistence throws, or values silently vanish on reload.
Cause: atom values are serialized for __atoms (SSR) and IndexedDB (persistence). Functions, class
instances, Map/Set, RegExp, BigInt, NaN/Infinity, and circular references do not survive.
Fix: store plain JSON-shaped data, primitives, arrays, plain objects. Reconstruct rich objects from plain data where you use them.
5. Seeding per-user data into a cached static page
Symptom: users see another user's data on first load.
Cause: a static route's HTML is cached and shared. Per-user values in its ssr().__atoms get baked
into the shared HTML.
Fix: seed only non-personal defaults into cached pages and personalise on the client; or use a dynamic/shell route for per-user content. See Mimir → SSR & hydration.
6. Reading browser globals during render
Symptom: hydration mismatch warnings, a flash of wrong content.
Cause: reading window/localStorage/Date.now() during render produces different output on the server
(where they do not exist) than on the client.
Fix: render a stable value first and update in an effect, or seed the value through an atom in ssr().
For theme specifically, also use the no-FOUC script. See SSR → hydration.
7. Using useState for state that should persist
Symptom: state resets every time the user navigates or refreshes.
Cause: useState lives in the component tree, which is torn down on navigation/reload.
Fix: if it should survive a navigation or refresh, put it in an atom (with a session for refresh). Keep
useState for genuinely local, disposable state.
8. Forgetting to unsubscribe
Symptom: a slow memory leak, or callbacks firing for unmounted things.
Cause: mimir.subscribe(...) returns an unsubscribe function you did not call.
Fix: store and call it. In React effects, return the unsubscribe so React cleans it up on unmount. (The
hooks already do this; only manual subscriptions need care.)
9. Accidental selector cycles
Symptom: Mimir: circular selector dependency at '...'.
Cause: selector A reads selector B which (transitively) reads A.
Fix: break the loop, derive both from a shared atom instead of from each other.
10. Expecting useSetAtom to re-render
Symptom: a component using useSetAtom does not update when the atom changes elsewhere.
Cause: useSetAtom is write-only; it deliberately does not subscribe (that is its performance
benefit).
Fix: if the component also needs to read and react, use useAtom or useAtomValue.
That completes the Mimir chapter. From here: the UI scaffolding, Pages & Layouts, the server surface, API Routes, or build it all in the Tutorial.