SpikeMe

Sample document

A real spike generated by SpikeMe — this is exactly what you get.

Spike: Global state management (React 19 + Vite)

Project acme-dashboard (React 19.1, Vite 6, TypeScript 5.6, pnpm). Today state lives in prop drilling and a few useContext calls; as the dashboard grew, re-renders and cross-screen coupling became friction. This spike compares three approaches for a global store, anchored to the real stack.

Objective and decision criteria

  • Bundle: gzip weight added to first load (tight budget).
  • Curve and DX: how fast the team ships without boilerplate.
  • Mental model: single store vs. atoms; re-render predictability.
  • Maturity: adoption, active maintenance, middleware ecosystem.
  • Compatibility: React 19 + Vite + strict TS, no hacks.

Options analysed

Option 1 — Zustand

Store as a hook, outside the React tree; selectors control re-renders.

import { create } from "zustand";
type Store = { count: number; inc: () => void };
export const useStore = create<Store>((set) => ({
  count: 0,
  inc: () => set((s) => ({ count: s.count + 1 })),
}));

Pros: minimal API, no provider, lean selectors, great TS. Cons: selector discipline is on the team; unopinionated about architecture.

Option 2 — Jotai

State as composable atoms; only atom readers re-render.

import { atom, useAtom } from "jotai";
const countAtom = atom(0);
export const useCount = () => useAtom(countAtom);

Pros: natural granularity, excellent for derived state. Cons: many atoms scatter logic; steeper conceptual curve.

Option 3 — Redux Toolkit

Predictable store with slices, devtools and standard middleware.

Pros: strong conventions, mature devtools and RTK Query, scales for big teams. Cons: more boilerplate and weight; overkill for a mid-size dashboard.

Comparison

CriterionZustandJotaiRedux Toolkit
Bundle (gzip)~1.1 kB~3.5 kB~13 kB (+react-redux)
Downloads/wk (npm)~5.5 M~1.6 M~4.8 M
Boilerplateminimallowmedium-high
Mental modelsingle storeatomsstore + slices
LicenseMITMITMIT

Figures collected on 2026-07-12 (npm registry, npm downloads, bundlephobia). Verify before deciding — the ecosystem moves.

Recommendation

Adopt Zustand. For a mid-size React 19 SPA it strikes the best balance: negligible bundle, zero provider and a mental model the team absorbs in an afternoon. The current friction (prop drilling and re-renders) is solved with a store plus selectors, without changing the app's architecture.

Reconsider if: state is mostly derived and granular (Jotai shines there), or the team is already large and standardised on Redux with heavy RTK Query use — then Redux Toolkit's conventions pay for the extra weight.

Proof-of-concept plan

  1. pnpm add zustand and create src/store/ui.ts with a real slice (e.g. dashboard filters). Timebox: 2h
  2. Migrate ONE screen from prop drilling to the store, measuring re-renders with the React DevTools Profiler. Timebox: 3h
  3. Add persistence (persist) only for what must survive reload. Timebox: 1h
  4. Review: compare bundle before/after and validate selectors. Timebox: 1h

Risks and mitigation

RiskMitigation
Poor selectors cause extra re-rendersSelector lint + review in the PoC PR
Store becomes a "global bucket"Slices per domain; no server logic in the store
Server state in the wrong placeKeep data-fetching in React Query, store for UI only

Spike acceptance criteria

  • One screen migrated with re-renders measured before/after
  • Bundle difference documented
  • Slice convention written in the team README
  • Decision recorded (this document) in the repository