web-state-zustand-9baa19 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-state-zustand-9baa19 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Zustand owns state two or more components read;useStateowns state one component reads; the URL owns filters and search; Context injects singletons and holds no state. Zustand v5 changes three things:useShallowfromzustand/react/shallowreplaces the old equality-function second argument, selectors must return stable references or they loop, andpersistno longer captures initial state at creation.
Detailed Resources:
useShallow, the Context anti-pattern, URL state<critical_requirements>
Move a value into a store as soon as a second component reads it. Passing it down instead couples every component on the path, and relocating it later means unpicking that chain.
Select one value per `useStore` call. The component then re-renders when that value changes and at no other time; a call with no selector subscribes to the whole store.
Return a stable reference from every selector. An object or function built inside the selector is a new reference on each call, which in v5 re-renders without end.
Persist preferences and nothing else, through `partialize`. Transient UI restored from storage — a modal that reopens itself, a sidebar that remembers being closed — reads as a bug.
Keep server data out of the store. Cached, invalidated, refetched data wants a layer built for it; a store gives you a copy that goes stale silently.
</critical_requirements>
Auto-detection: create from zustand, zustand/middleware, zustand/react/shallow, useShallow, createWithEqualityFn, zustand/traditional, persist, partialize, devtools, store slices
Applies to:
useState, the URL and Context for a given valuedevtools and persistHandled elsewhere:
<philosophy>
Zustand is a subscription primitive with a hook attached. Its performance comes entirely from the selector: the store notifies every subscriber on every change, and the selector is what decides whether that notification becomes a render. So store design is selector design.
toggleSidebar(); the store decides what toggling means</philosophy>
<decision_framework>
| The value | Belongs in | Because |
|---|---|---|
| Read by 2+ components | a store | selective re-renders, and no prop chain to unpick later |
| Read by 1 component | useState | nothing else can observe it, so nothing else needs to |
| A filter, query, page or sort | the URL | shareable, bookmarkable, and the back button works |
| A singleton set at startup | Context | it never changes, so the re-render cost never arises |
| Fetched from an API | not this skill | it needs caching and invalidation, which a store has none of |
Context is the row people get wrong. It is a transport for a value that does not change, and using it for state that does re-renders every consumer on every change with no way to opt out.
</decision_framework>
<patterns>
devtools for inspection, persist for the fields that should outlive the tab, partialize to keep everything else out of storage.
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
theme: DEFAULT_THEME,
sidebarOpen: true,
toggleSidebar: () =>
set((state) => ({ sidebarOpen: !state.sidebarOpen })),
}),
{ name: UI_STORAGE_KEY, partialize: (state) => ({ theme: state.theme }) },
),
),
);Full code: examples/core.md
One value per call is the default, and the reason the store is fast.
const toggleSidebar = useUIStore((state) => state.toggleSidebar);
const isOpen = useUIStore((state) => state.sidebarOpen);Full code: examples/core.md
At three or more values from one store, one useShallow call beats three subscriptions.
import { useShallow } from "zustand/react/shallow";
const { sidebarOpen, theme } = useUIStore(
useShallow((state) => ({
sidebarOpen: state.sidebarOpen,
theme: state.theme,
})),
);Full code: examples/core.md
Context carries a value that never changes — a client, a connection, a configuration read at startup.
const DatabaseContext = createContext<Database | null>(null);Full code: examples/core.md
Filters, search, pagination and sort live in the URL, where they survive a reload and a paste into a colleague's chat.
const searchParams = new URLSearchParams(window.location.search);
const category = searchParams.get("category") ?? undefined;Full code: examples/core.md
</patterns>
<red_flags>
Breaks at runtime:
create() called with a second equality-function argument — removed in v5, so the argument is ignored and the comparison silently never happens. Use useShallow, or createWithEqualityFn from zustand/traditional.persist did not store it. Set it with setState after creation.Surprising behaviour:
useStore() with no selector subscribes to the entire store, so the component re-renders on changes to fields it never reads.useShallow compares the object, not the individual reads.?page=2 reads back as "2", and ?open=false is truthy until parsed.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.