React Hooks: useReducer
In a recent blog we showed how to build the demo app pictured below using functional components with React Hooks- useState. The demo app takes a student name and grade as inputs, and appends the inputs to a list.
The components tree of the demo app is showed below. The app has two child components; FunctionForm
and StudentContainer
. Student
is a child component of StudentContainer
multiple useState
- We used useState to pass in the original list/data to the
App
component - We used useState to managed state in the
FunctionForm
component via controlled form. - We used useState to handle form submission and to add input data from the form to existing list.
In this blog, we useReducer to managed state instead of using multiple useState mentioned in the list above.
[
useReducer
is ] An alternative touseState
. [useReducer
] Accepts a reducer of type(state, action) => newState
, and returns the current state paired with adispatch
method. (If you’re familiar with Redux, you already know how this works.)
useReducer
is usually preferable touseState
when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
In the following sections, we show how to replace each useState with a reducer type paired with a dispatch method. But first, here is what the App
component and reducer function looks like;
app component
In this component, the useReducer takes an initialState const initialState={students: [], name: "", grade: ""}
and a reducer type, paired with a dispatch method and returns the variable’s current state — students, name, grade
The application state is in the App
component. We pass down callback function and pertinent state variables to FunctionForm
component to remotely managed state from the App
component. The students variable is passed to the studentsContainer, where each student is passed down to the student component.
reducer function
The reducer function handles three action cases; “addData”, “inputField” and “addOneStudent” for loading data, controlled form and form submission respectively.
load/render data
We used useEffect to trigger a dispatch action that loads the data into the App
component. This dispatch action is triggered when the page first mounts or after any subsequent page rendering. The reducer type “addData” paired with dispatch action is also shown below. We did not have to useEffect in this scenario, we could have just set the initialState.students
to local data. If we were to fetch data from an external API, it would be done with useEffect.
useEffect(() => {
dispatch({ type: "addData", data });
}, []);// reducer to handle "addData" action type
case "addData":
return { ...state, students: action.data };
with useState we would have done something like;
const [students, setStudents] = useState(data);
OR
const [students, setStudents] = useState([]);
useEffect(() => {
setStudents(data);
}, []);
controlled form
We useReducer to handled controlled form and the corresponding operation within the reducer function is shown below
const handleChange = (name, value) => {
dispatch({ type: "inputField", fieldname: name, value: value });
};// reducer to handle "inputField" action type
case "inputField":
return { ...state, [action.fieldname]: action.value };
with useState, we would have called the useState function twice and passed-in two separate callback functions to the form’s input onChange attribute.
const [name, setName] = useState("");
const [grade, setGrade] = useState("")const handleNameChange = (e) => {
setName(e.target.value);
};
const handleGradeChange = (e) => {
setGrade(e.target.value);
}
form submission
The following dispatch action shown below is triggered when the form is submitted. The corresponding operation within the reducer function is shown below. A new student object is appended to the exciting students list without mutating state.
const addStudents = (info) => {
dispatch({ type: "addOneStudent", info });
};// reducer to handle "addOneStudent" action type
case "addOneStudent":
let newId = state.students.length + 1;
let newStudent = {
grade: action.info.grade,
name: action.info.name,
id: newId,
};
return { ...state, students: [...state.students, newStudent] };
The useState version, shown below, would look similar to how we handled form submission with useReducer
const [students, setStudents] = useState(data);
const addStudentFunction = (info) => {
let newId = students.length + 1;
let newStudent = { ...info, id: newId };
let newStudents = [...students, newStudent];
setStudents(newStudents);
};
Summary.
- we discussed when and how to replace useState with useReducer.
- we covered useReducer in react controlled forms.
The github repository for the demo app is here