new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Redux Toolkit · API · all subjects

redux dataflow

20 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Redux dataflow core pattern: dispatch events not setters

Actions should describe what happened in the UI as events rather than generic setter commands. Instead of dispatching a generic action like setPosts with a precomputed next state, dispatch event-style actions like postAdded or postPublished that name the specific event. Reducers then own the state transition logic.

Redux dataflow pattern: let reducers combine store state with new data

When a state transition requires combining current store state with new external data, dispatch only the new external data and let the reducer own the merge logic. Do not combine state before dispatching. Only authoritative external snapshots should replace state wholesale.

Redux dataflow pattern: derive values with selectors instead of storing duplicates

Use createSelector to derive view shapes from state rather than storing derived values in state. Selectors keep a single source of truth in the slice state while still exposing the exact shapes the UI needs. Derived values stored in state drift out of sync quickly.

Critical mistake: mutating selected state outside reducers

Do not mutate objects read from the store outside of reducers. Objects returned by selectors are still store state; mutating them breaks immutability and stale-render assumptions. Instead, dispatch an action so the reducer can handle the state update.

High priority mistake: using setter-style actions instead of event-style

Avoid dispatcher actions that ask reducers to blindly replace state with a precomputed value, such as dispatching setPosts with a next array computed in the component. Instead, dispatch event-style actions like postAdded that describe what happened.

High priority mistake: combining store state before dispatch

Do not select current store state, merge it with incoming data in the component, and then dispatch the merged result. Let the reducer own the merge logic by dispatching only the incoming external data.

High priority mistake: ignoring current state in async reducers

Async reducers that treat every lifecycle action as valid can move the slice into impossible states or let stale requests win. Check the current state before updating it. For example, only update state if status is 'pending' when handling a fetchPosts.fulfilled action.

Medium priority mistake: storing derived values in state

Do not store both raw values and computed derived values in state, such as storing both items and visiblePosts. Derived values drift out of sync quickly. Keep only the raw state and use createSelector to derive the view shape.

Redux dataflow example: complete setup with selectors and dispatch

import { configureStore, createSelector, createSlice } from '@reduxjs/toolkit' const postsSlice = createSlice({ name: 'posts', initialState: { items: [] as { id: string; title: string; published: boolean }[], filter: 'all' as 'all' | 'published', }, reducers: { postAdded(state, action: { payload: { id: string; title: string } }) { state.items.push({ ...action.payload, published: false }) }, postPublished(state, action: { payload: { id: string } }) { const post = state.items.find((item) => item.id === action.payload.id) if (post) { post.published = true } }, filterChanged(state, action: { payload: 'all' | 'published' }) { state.filter = action.payload }, }, }) const store = configureStore({ reducer: { posts: postsSlice.reducer, }, }) type RootState = ReturnType<typeof store.getState> const selectPostsState = (state: RootState) => state.posts const selectVisiblePosts = createSelector([selectPostsState], (postsState) => postsState.filter === 'all' ? postsState.items : postsState.items.filter((post) => post.published), ) store.dispatch(postsSlice.actions.postAdded({ id: 'p1', title: 'Draft' })) store.dispatch(postsSlice.actions.postPublished({ id: 'p1' })) const visiblePosts = selectVisiblePosts(store.getState()) console.log(visiblePosts)

Redux dataflow example: event-style action reducers

const postsSlice = createSlice({ name: 'posts', initialState: [] as { id: string; title: string }[], reducers: { postAdded(state, action: { payload: { id: string; title: string } }) { state.push(action.payload) }, postRemoved(state, action: { payload: { id: string } }) { return state.filter((post) => post.id !== action.payload.id) }, postUpdated( state, action: { payload: { id: string; changes: Partial<{ title: string }> } }, ) { const post = state.find((item) => item.id === action.payload.id) if (post && action.payload.changes.title) { post.title = action.payload.changes.title } }, }, }) postsSlice.actions.postAdded({ id: 'p1', title: 'Draft' })

Redux dataflow example: using createEntityAdapter with reducer merge logic

import { createEntityAdapter, createSlice } from '@reduxjs/toolkit' const postsAdapter = createEntityAdapter<{ id: string; title: string }>() const postsSlice = createSlice({ name: 'posts', initialState: postsAdapter.getInitialState(), reducers: { postsReceived(state, action: { payload: { id: string; title: string }[] }) { postsAdapter.upsertMany(state, action.payload) }, }, }) const incomingPosts = [ { id: 'p1', title: 'Draft' }, { id: 'p2', title: 'Published' }, ] postsSlice.actions.postsReceived(incomingPosts)

Redux dataflow example: using createSelector for derived data

import { createSelector } from '@reduxjs/toolkit' const selectPosts = (state: RootState) => state.posts.items const selectFilter = (state: RootState) => state.posts.filter export const selectVisiblePosts = createSelector( [selectPosts, selectFilter], (posts, filter) => filter === 'all' ? posts : posts.filter((post) => post.published), )

Add createApi reducer and middleware to configureStore

The generated slice reducer and middleware from createApi must both be added to the Redux store setup in configureStore to work correctly. Add the reducer using the reducerPath as the top-level slice key, and add the middleware to the middleware chain.

Example: configuring RTK Query with configureStore

import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query' import { pokemonApi } from './services/pokemon' export const store = configureStore({ reducer: { [pokemonApi.reducerPath]: pokemonApi.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(pokemonApi.middleware), }) setupListeners(store.dispatch) This example shows how to add the API slice reducer using reducerPath as the key and concat the API middleware to the default middleware chain.

Name Redux state after data concepts, not component names

Store keys should describe data or domain concepts (e.g., 'auth', 'posts'), not the current component tree or UI structure (e.g., 'loginScreen', 'postsList'). Naming after components couples the state shape to the component hierarchy.

Do not blindly spread action payloads into state

Reducers should not use the spread operator to merge entire action payloads into state (e.g., { ...state, ...action.payload }). Instead, reducers should own the slice shape and explicitly assign trusted properties from the payload. This prevents unexpected properties in the payload from modifying state.

State ownership heuristics: default owner and tool by data kind

Different kinds of state have recommended owners and tools: Editable form fields are owned by the component and typically use useState. Shared mutable app data is owned by Redux and uses slice state. Server cache is owned by RTK Query and uses createApi. URL, pathname, and search params are owned by the router and use router APIs plus selector inputs. Browser-only authority like localStorage is owned by an external source and is read at boundaries, then dispatch events.

Good reasons to move data into Redux

Move data into Redux when multiple distant parts of the UI need the same mutable data, when you need time-travel debugging or a stable action history, or when the reducer should own transitions because they mix old store state with new inputs.

Reasons to keep data out of Redux

Keep data out of Redux when another system already owns it such as the router, when it only matters during editing inside one component tree, or when it is server cache and RTK Query fits the use case better.

Side effects must not run inside reducers

Reducers must stay pure and must not contain side effects like fetch calls, even when Immer is available. Side effects like API calls must be moved to createAsyncThunk, listener middleware, or other external logic. Violating this breaks predictability and serialization.

Give your agent this brain