useReducer is a built-in React Hook for components whose state contains several related values or whose update logic is becoming difficult to follow. Instead of setting each value separately, the component dispatches an action; a reducer uses the current state and action to produce the next state. section 3.0 section 3.2
Use the Hook by connecting three pieces:
- Pass a reducer and initial state to receive the current state and a dispatch function. section 3.2.3
- Dispatch actions from event handlers instead of directly updating individual state values. section 3.2.3 section 3.4
- Handle action types in the reducer and return a new state; use an object with type and payload for nontrivial actions. section 3.5
Managing a bookings list
A BookablesList component tracks the selected group, bookable index, bookable data, and whether details are visible. Its setup is `const [state, dispatch] = useReducer(reducer, initialState);`. A checkbox can dispatch `{ type: "TOGGLE_HAS_DETAILS" }`, while other handlers dispatch actions for switching groups or selecting bookables. React passes the action and current state to the reducer, then calls the component again with the updated state. section 3.2.3 section 3.4
Generating initial state
When initial state requires computation, pass an initialization argument and function: `const [week, dispatch] = useReducer(reducer, date, getWeek);`. The WeekPicker component uses `getWeek` to create its initial week from a date, then dispatches actions such as `PREV_WEEK` and `TODAY` for navigation. section 3.3.4 section 3.3
Listing 3.5 The BookablesList component using a reducer
import {useReducer, Fragment} from "react"; #1
import {bookables, sessions, days} from "../../static.json";
import {FaArrowRight} from "react-icons/fa";
import reducer from "./reducer"; #2
const initialState = { #3
group: "Rooms", #3
bookableIndex: 0, #3
hasDetails: true, #3
bookables #3
}; #3
export default function BookablesList () {
const [state, dispatch] = useReducer(reducer, initialState); #4
const {group, bookableIndex, bookables, hasDetails} = state; #5
const bookablesInGroup = bookables.filter(b => b.group === group);
const bookable = bookablesInGroup[bookableIndex];
const groups = [...new Set(bookables.map(b => b.group))];
function changeGroup (e) {
dispatch({ #6
type: "SET_GROUP", #6
payload: e.target.value #6
}); #6
}
function changeBookable (selectedIndex) {
dispatch({
type: "SET_BOOKABLE",
payload: selectedIndex
});
}
function nextBookable () {
dispatch({ type: "NEXT_BOOKABLE" }); #7
}
function toggleDetails () {
dispatch({ type: "TOGGLE_HAS_DETAILS" });
}
return (
<Fragment>
<div>
// group picker
<ul className="bookables items-list-nav">
{bookablesInGroup.map((b, i) => (
<li
key={b.id}
className={i === bookableIndex ? "selected" : null}
>
<button
className="btn"
onClick={() => changeBookable(i)} #8
>
{b.title}
</button>
</li>
))}
</ul>
// Next button
</div>
{bookable && (
<div className="bookable-details">
<div className="item">
<div className="item-header">
<h2>
{bookable.title}
</h2>
<span className="controls">
<label>
<input
type="checkbox"
checked={hasDetails}
onChange={toggleDetails} #9
/>
Show Details
</label>
</span>
</div>
<p>{bookable.notes}</p>
{hasDetails && (
<div className="item-details">
// details
</div>
)}
</div>
</div>
)}
</Fragment>
);
}
#1 Import the useReducer hook.
#2 Import the reducer from listing 3.4.
#3 Specify an initial state.
#4 Call useReducer, passing the reducer and the initial state.
#5 Assign state values to local variables.
#6 Dispatch an action with a type and a payload.
#7 Dispatch an action that doesn’t need a payload.
#8 Call the new changeBookable function.
#9 Call the new toggleDetails function.
Inspect the complete BookablesList implementation, including imports, initial state, reducer setup, and state destructuring. section 3.2.3


