For large-scale applications with top-down data flow, avoid a single monolithic reducer. Instead, define separate reducers for each child component and encapsulate child actions within a parent action. This allows the parent to manage child state without needing to know every individual child action.
Component File Structure
Typically, a component file should define:
type ActionT: A union type of all actions the component can dispatch.type StateT: A read-only type describing the component's state.type PropsT: Usually includes dispatch ((ActionT) => void) and state (StateT).function createInitialState(...): Initializes StateT.function reducer(state: StateT, action: ActionT): Handles state transitions via a switch statement.
State Immutability
StateT must be deeply read-only. Use the spread operator for shallow copies or the mutate-cow library for complex, deeply-nested updates.
Encapsulating Child Actions
To call a child reducer from a parent, wrap the child's action in a parent action type (e.g., update-child).
Example of parent-child reducer integration:
import {
type ActionT as ChildActionT,
reducer as childReducer,
} from './Child.js';
type ActionT =
| {readonly type: 'update-child', readonly action: ChildActionT}
// ...
;
function reducer(state: StateT, action: ActionT): StateT {
match (action) {
{type: 'update-child', const action} => {
const childAction = action;
state.child = childReducer(state.child, childAction);
}
}
}
function ParentComponent(props: PropsT) {
const [state, dispatch] = React.useReducer(
reducer,
props,
createInitialState,
);
const childDispatch = React.useCallback((action: ChildActionT) => {
dispatch({type: 'update-child', action});
}, [dispatch]);
return <Child dispatch={childDispatch} />;
}
import {
type ActionT as ChildActionT,
reducer as childReducer,
} from './Child.js';
type ActionT =
| {readonly type: 'update-child', readonly action: ChildActionT}
// ...
;
function reducer(state: StateT, action: ActionT): StateT {
match (action) {
{type: 'update-child', const action} => {
const childAction = action;
state.child = childReducer(state.child, childAction);
}
}
}
function ParentComponent(props: PropsT) {
const [state, dispatch] = React.useReducer(
reducer,
props,
createInitialState,
);
const childDispatch = React.useCallback((action: ChildActionT) => {
dispatch({type: 'update-child', action});
}, [dispatch]);
return <Child dispatch={childDispatch} />;
}