Redux Toolkit (RTK) is a leaner way to use Redux for React data management. Classic Redux could require substantial setup even for a small list; RTK reduces that boilerplate while retaining a store-and-reducer approach. It is particularly worth considering when application data needs many coordinated updates, but it is not a default requirement for every app. section 8.0 section 8.9
The mental model is close to React’s useReducer: reducers describe updates, and actions trigger them. RTK builds Immer into its reducers, allowing update logic written against a draft. In the goal-tracking implementation, reducers add and remove goals and change which goal is selected. section 8.5 section 8.5.2
Listing 8.17 src/data/store.js
import { v4 as uuid } from "uuid";
import { createSlice, configureStore } from "@reduxjs/toolkit";
export const STORAGE_KEY = "100-things-redux";
const getInitialThings = () =>
JSON.parse(localStorage.getItem(STORAGE_KEY)) || {
things: [],
currentThing: null,
};
const reducers = {
seeThing: (draft, { payload: newThing }) => { #1
draft.currentThing = newThing; #1
}, #1
seeAllThings: (draft) => { #1
draft.currentThing = null; #1
}, #1
addThing: (draft, { payload: name }) => { #1
draft.things.push({ id: uuid(), name, done: [] }); #1
}, #1
removeThing: (draft, { payload: id }) => { #1
const index = draft.things.findIndex((thing) => thing.id === id); #1
if (index !== -1) { #1
draft.things.splice(index, 1); #1
if (id === draft.currentThing) { #1
draft.currentThing = null; #1
} #1
} #1
}, #1
doThing: (draft, { payload: id }) => { #1
const thing = draft.things.find((thing) => thing.id === id); #1
thing.done.push(Date.now()); #1
}, #1
undoThing: #1
(draft, { payload: { id, index } }) => { #1
const thing = draft.things.find((thing) => thing.id === id);
thing.done.splice(index, 1);
},
};
const dataSlice = createSlice({ #2
name: "data",
initialState: getInitialThings(),
reducers,
});
export const store = configureStore({ #3
reducer: {
data: dataSlice.reducer,
},
});
export const actions = dataSlice.actions; #4
Inspect the store setup and draft-based reducers for concrete RTK update logic. section 8.5.2
Keep the data layer distinct from the components that consume it:
- Provide the store to the React tree with react-redux’s Provider. section 8.5.2
- Read selected values with useSelector, optionally behind application-specific hooks. section 8.5.2
- Use useDispatch to send generated actions from hooks that perform updates. section 8.5.2
Listing 8.21 src/data/useThing.js
import { useData } from "./useData";
import { actions } from "./store";
import { useDispatch } from "react-redux";
export function useThing(id) {
const thing = useData((store) =>
store.data.things.find((t) => t.id === id)
);
const dispatch = useDispatch(); #1
return {
thing,
seeThing: () => dispatch(actions.seeThing(id)),
removeThing: () => dispatch(actions.removeThing(id)),
doThing: () => dispatch(actions.doThing(id)),
seeAllThings: () => dispatch(actions.seeAllThings()),
undoThing: (index) => dispatch(actions.undoThing({ id, index })),
undoLastThing: () =>
dispatch(actions.undoThing({ id, index: thing.done.length - 1 })),
};
}
See how one hook combines a selected goal with dispatch functions for viewing, removing, completing, and undoing it. section 8.5.2
Persistence is a separate implementation decision. The goal tracker reads its initial data from localStorage when defining the slice, then uses store.subscribe to write the data after store updates. RTK’s reducers do not, by themselves, provide this persistence. section 8.5.2
Choose by fit rather than habit. Context with useReducer can go far without another state library; RTK trims Redux’s setup but retains some legacy boilerplate. Zustand is simpler but offers fewer built-in patterns for organizing multiple unrelated stores. A state-machine approach such as XState may better clarify a complex flow. section 8.0 section 8.9

