State ownership, context, and reducers
Place state near its users, share it deliberately, and model complex transitions with a reducer.
Keep state close to where it is used
Start with the component that needs a value. If sibling components need the same state, move it to their nearest common parent and pass the value and event handlers as props. This is called lifting state up. Avoid duplicating a value in multiple places when one source of truth will do.
Context is for shared values
Context lets a parent provide a value to descendants without threading it through every intermediate component. It can help with values such as the current theme or signed-in user. It is not automatically a better replacement for props: keep simple, local data local.
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
Reducers make transitions explicit
When several actions update related state, use a reducer to define the transition from current state and action to next state:
function reducer(state, action) {
switch (action.type) {
case 'added':
return [...state, action.task];
case 'removed':
return state.filter((task) => task.id !== action.id);
default:
return state;
}
}
Reducers should be pure and return new values. Do not use context or a reducer solely to make a small component appear more abstract.
Exercise: Add a filter to the task list. Decide which component owns the filter and which components need to read it. Use a reducer only if the set of transitions benefits from being centralized.