Skip to content

feat(redux): state management chapter (RTK) - #3

Open
Demonkratiy wants to merge 18 commits into
devfrom
feat/state-management
Open

feat(redux): state management chapter (RTK)#3
Demonkratiy wants to merge 18 commits into
devfrom
feat/state-management

Conversation

@Demonkratiy

Copy link
Copy Markdown
Owner

State management chapter (Redux Toolkit)

Introduces global state management with Redux Toolkit, migrating the TODO domain from local useState to a Redux store, on top of the FSD structure.

Base branch: targets feat/fsd-restructure (PR #2), not dev, so the diff shows only the Redux chapter. Stack: dev → PR #2 (FSD) → this. Merge order: PR #2 first, then retarget/rebase this onto dev.

Educational scope: Redux is intentionally overkill for a TODO this small (shallow tree, prop drilling wasn't painful). It's introduced to learn the canonical, most explicit Flux implementation — not because the app needs it. This is called out in the wiki.

What changed (by step)

  1. RTK setupapp/store.ts (configureStore + setupStore factory + RootState/AppDispatch), typed hooks app/hooks.ts, <Provider> in app/main.tsx.
  2. todos sliceentities/todo/model/todoSlice.ts (todoAdded/todoToggled/todoDeleted, Immer), wired into HomePage.
  3. filter slicefeatures/filter-todos/model/filterSlice.ts (filterChanged).
  4. SelectorsselectTodos, selectFilterStatus, and a memoized selectFilteredTodos (createSelector).
  5. Remove prop drillingAddTodoForm, TodoFilter, TodoList connect to the store via hooks; TodoItem stays presentational; HomePage reduces to pure composition.

Architecture notes

  • FSD ↔ Redux: store + typed hooks live in app/; a documented boundaries exception (rule 1c in eslint.config.js) lets any layer import @/app/store and @/app/hooks (cross-cutting infra). Cleaner reducer-injection alternative noted in the wiki.
  • Connected vs presentational: shared/ui + entities stay dumb (Storybook-friendly); features/widgets are connected.
  • Storybook + Redux: global Provider decorator + setupStore + per-story parameters.preloadedState (real store, not a mock).

Docs (wiki, Russian)

  • state-management.md — Flux theory, glossary, decision tree
  • redux-setup.md — store, hooks, Provider, slice, static store overview, connected-vs-presentational, Storybook+Redux
  • redux-concepts.md — actions, payload, Immer, reducer-local vs selector-global, selectors, entity-vs-feature
  • state-managers-comparison.md — same domain in Redux vs Zustand vs MobX (docs only; not implemented)

Verification

  • npm run build
  • npm run lint ✅ (FSD boundaries enforced)
  • npm run build-storybook
  • npm run format:check

Restore the state management theory page (Flux, glossary, library landscape, decision tree) authored before the FSD detour. Update stale App references to HomePage and align the chapter plan with FSD slice placement (todoSlice in entities/todo/model, filterSlice in features/filter-todos/model, store in app/). Add README entry.
Install @reduxjs/toolkit and react-redux. Add app/store.ts (configureStore + RootState/AppDispatch), app/hooks.ts (typed useAppSelector/useAppDispatch), wrap the app in <Provider>, and add entities/todo/model/todoSlice.ts with todoAdded/todoToggled/todoDeleted. Add a documented boundaries exception so any layer may import @/app/store and @/app/hooks (the store is cross-cutting infra). Components are not wired to the store yet.
Explain RTK install, configureStore, typed hooks, Provider, the first slice (createSlice + Immer + prepare), FSD placement and the app-store boundary exception with the reducer-injection alternative noted.
Replace HomePage's local todos useState with useAppSelector(state => state.todos.items) and dispatch todoAdded/todoToggled/todoDeleted via useAppDispatch. The todos source of truth now lives in Redux (inspectable in DevTools). Filter stays on useState until the next step. Leaf components remain prop-driven; prop drilling removal comes later.
Add a section on how to see the whole store statically (store.ts as the domain map, RootState as the full shape, slice files as per-domain single files, index.ts as public API). Refresh the 'what's next' section now that todos is wired to the store.
Add features/filter-todos/model/filterSlice.ts with a filterChanged reducer over { status: FilterStatus }, and expose filterReducer/filterChanged through the feature public API.
Register filterReducer in the store (state.filter) and replace HomePage's filter useState with useAppSelector(state => state.filter.status) + dispatch(filterChanged). Both todos and filter now live in Redux.
Add redux-concepts.md explaining actions, payload (incl. payload vs state shape and prepare), Immer as a standalone library embedded in RTK, and entity-vs-feature action ownership. Link it from README and the setup page.
Add selectTodos (entities/todo), selectFilterStatus and a memoized selectFilteredTodos (features/filter-todos, via createSelector composing selectTodos + selectFilterStatus). Expose them through the slice public APIs.
Replace HomePage's inline todos filtering with useAppSelector(selectFilteredTodos) and read the filter via selectFilterStatus. The derivation logic and knowledge of store shape now live in the selectors, not the component.
Add a section clarifying that the reducer's state is the local slice while a selector's state is the global RootState, where the mount key comes from, and field-vs-reducer anatomy.
Extract rootReducer via combineReducers and add setupStore(preloadedState) so tests and Storybook get isolated stores. Add a global Storybook decorator wrapping every story in a fresh Provider, seedable per-story via parameters.preloadedState.
AddTodoForm, TodoFilter and TodoList read/dispatch via typed hooks instead of props (TodoItem stays presentational). Stories use the Provider decorator and preloadedState. Removes prop drilling from these components.
With features/widget connected to the store, HomePage no longer owns state, handlers or selectors — it just composes AddTodoForm, TodoFilter and TodoList with no props.
Document selectors (basic vs derived, createSelector memoization, FSD placement) in redux-concepts. Add connected-vs-presentational layer guidance and a Storybook-for-connected-components section (setupStore, Provider decorator, preloadedState) to redux-setup.
Compare the same todo domain across Redux (RTK), Zustand and MobX with illustrative snippets, a differences table and when-to-pick guidance. Zustand/MobX are not implemented in the project — documentation only.
@Demonkratiy
Demonkratiy changed the base branch from feat/fsd-restructure to dev July 26, 2026 16:22
Replace the three separately-connected components (AddTodoForm,
TodoFilter, TodoList) with a single connected TodoWidget driven by
one facade hook (useTodoWidget). AddTodoForm/TodoFilter are
presentational again (props-only); widgets/todo-list is removed.
Add widget-facade-hook.md (composite-hook rationale, end-to-end
Redux trace, MobX caveat) and react-hooks-fundamentals.md (rules of
hooks mechanism, custom hooks, hook-call vs returned-values). Link
both from README and update the fsd-architecture.md TodoList note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant