Frontend State Management concept page covering server state (TanStack Query), client/UI state (Zustand), URL state (Next.js searchParams), and form state (React Hook Form). Includes 2 ADRs (Zustand vs Redux Toolkit vs Jotai for 2026; TanStack Query vs SWR vs custom useEffect) and 3 scenarios: local state lifted to Zustand, server state cache+refetch+mutation+invalidation, URL state filters with TanStack cache reuse.
Главная ошибка во frontend state management - считать, что "state" является одной категорией. В реальном приложении есть как минимум четыре разных класса: server state, client/UI state, URL state и form state. У них разные владельцы, разные жизненные циклы и разные failure modes. Если смешать их в одном Redux/Zustand store, команда получает лишние re-render, рассинхрон кэшей, невозможность поделиться ссылкой с фильтрами и useEffect-боль на каждом экране.
Server state - это удаленные данные: профиль, список заказов, результаты поиска. Они async, shared, cached и stale по определению: другой клиент мог изменить их секунду назад. Client state - это состояние интерфейса: открыт ли sidebar, какая тема выбрана, какая модалка активна. URL state - это состояние, которое пользователь ожидает сохранить в ссылке: фильтры, page, search query, выбранная вкладка. Form state - это отдельная дисциплина с dirty/touched/errors, validation и submit lifecycle.
Урок нужен, чтобы перестать выбирать библиотеку по бренду и начать классифицировать данные. Тогда TanStack Query, Zustand, URLSearchParams и React Hook Form не конкурируют друг с другом, а занимают свои границы.
Правильный вопрос не "Redux или Zustand?", а "какой это state?".
useState, useReducer, Zustand, Jotai.URLSearchParams, Next.js searchParams, nuqs, React Router search params.Derived state лучше вычислять, а не хранить. Persistent local state - отдельный подвид client state: theme можно положить в localStorage, но список пользователей от API нельзя зеркалить туда без стратегии синхронизации.
Диаграмма делит canvas на группы: React component tree, TanStack Query cache, Zustand store, URL state, form state и network/persistence. App, Header, Sidebar, Page и Modal показывают потребителей. tq-cache, tq-fetcher и tq-mutations показывают server-state pipeline: cache key, fetch, mutation, invalidation. zs-store, zs-persist, zs-devtools показывают UI state store и опциональную персистентность. url-router и url-bar показывают, что фильтр может жить в адресе, а не в компоненте. rhf, rhf-fields и rhf-submit выделяют form state как отдельный слой, который не надо складывать в глобальный store.
ADR-001 висит на root App и сравнивает Zustand, Redux Toolkit и Jotai. ADR-002 сравнивает TanStack Query, SWR и custom useEffect для server state. Это важно: диаграмма не говорит "одна библиотека решит все", она показывает ownership boundaries.
Local state lifted to Zustand показывает, как локальная настройка UI становится глобальной только после появления реального sharing. Theme, sidebar collapsed и notification queue читаются несколькими компонентами, поэтому Zustand store оправдан. Урок: не надо создавать appStore заранее. Начинайте с useState, поднимайте state, и только при реальном пересечении компонентов вводите store.
Server state cache + refetch + mutation + invalidation показывает TanStack Query lifecycle. Первый компонент получает cache miss и вызывает API. Второй компонент с тем же key не делает второй request, а переиспользует QueryClient. Mutation обновляет сервер и инвалидирует связанные keys. Урок: server state - это не "глобальные данные для Redux", а кэш удаленного источника с stale-while-revalidate, retry, dedup и invalidation.
URL state filters with TanStack cache reuse показывает фильтр ?status=open&page=2. URL меняется, query key включает фильтры, кэш хранит разные результаты для разных параметров. Пользователь может обновить страницу, отправить ссылку коллеге и вернуться назад через browser history. Урок: фильтры в useState ломают shareability и часто ухудшают UX.
Form submit through mutation связывает React Hook Form с server state: поля и ошибки живут в form layer, submit вызывает mutation, mutation инвалидирует server-state queries. Урок: форма не должна лежать в global store только потому, что в ней много полей.
ADR-001 выбирает Zustand как дефолт для greenfield React 19/Next 15 проекта, но с важной оговоркой: server state уже отдан TanStack Query, поэтому client store остается небольшим. Zustand выигрывает простотой, размером около нескольких KB gzipped, hooks API, middleware для persist/devtools и низким порогом для команды. Это не значит, что Redux Toolkit плох. RTK имеет смысл, если команда уже глубоко на Redux, если нужен строгий action/reducer discipline в большом enterprise UI, или если middleware ecosystem реально используется.
Jotai выигрывает, когда state granular и derived: много атомов, вычисляемых значений, realtime widgets, редакторы, drag/drop с live preview. Цена - другой mental model и риск распылить атомы по проекту без структуры. useState и useReducer остаются лучшим выбором для локального состояния: глобализировать state "на вырост" - архитектурный долг.
ADR-002 выбирает TanStack Query для REST/RPC server state. Причина в свойствах server state: stale, async, retryable, shared. Custom useEffect + fetch быстро превращается в ручную реализацию cache, retry, loading/error states, dedup и invalidation. SWR остается хорошим легким вариантом для простых Next.js сценариев, Apollo - для GraphQL, tRPC - для TypeScript monorepo, RTK Query - если Redux уже принят как платформа.
Главный trade-off: чем больше библиотек, тем больше surface area. Но разделение по ownership обычно снижает сложность: TanStack Query не знает про модалки, Zustand не знает про freshness API, URL не знает про validation формы.
Большинство современных React SaaS используют похожее разделение. TanStack Query или Apollo держит remote data; Zustand/Jotai/Redux Toolkit держит UI state; router/search params держит filters; React Hook Form или Formik держит формы. Next.js App Router дополнительно переносит часть server state в Server Components, но это не отменяет клиентский кэш для интерактивных зон. Linear-like dashboards часто живут на SPA/RSC поверхности с server cache и локальным UI store. E-commerce каталоги критично зависят от URL state для фильтров, иначе ссылки на выборку товаров не работают. Realtime/collab редакторы добавляют Yjs/Liveblocks как отдельный realtime state слой, а не заменяют им все остальное.
useEffect для каждого fetch: нет dedup, retry, stale-while-revalidate, background refresh и нормального invalidation.useState: хранится filteredItems, хотя его можно вычислить из items и filters; появляются рассинхроны.Не используйте глобальный state manager для состояния, которое нужно одному компоненту или двум соседним компонентам. Не используйте TanStack Query для чисто локального UI state: theme, open modal, selected tab без server persistence. Не используйте URL state для приватных временных данных, черновиков пароля, больших объектов и всего, что не должно попадать в историю браузера. Не используйте form library для двух простых input без validation, если native form и useState закрывают задачу. Не используйте Redux Toolkit только потому, что "проект станет большим": масштабирование начинается с границ домена и кэшей, а не с самого тяжелого store.
Внешние ориентиры: TanStack Query docs, Kent C. Dodds про application state, Dan Abramov "You Might Not Need Redux", Redux Toolkit docs, Daishi Kato materials по Zustand/Jotai/Valtio, XState docs и nuqs docs для type-safe URL state.