This article assumes some basic familiarity with the useReducer()
hook. Examples are using react-bootstrap
but you don't need to be using it in your own project for this to work.
Efficient VS Inefficient
Any DOM structure of HTML inputs would do, but let's say for example you have an HTML form such as the one above. You want React to update state for every change of the input by the user.
Inefficient
Assuming this state object...
const initState = {
firstName: "",
lastName: "",
street: "",
aptSuite: "",
city: "",
stateName: "",
zipcode: "",
date: "",
title: "",
status: "fillingOutForm",
};
Assuming a form input element structured like this...
<Form.Label htmlFor="user-first-name">First name</Form.Label>
<Form.Control
type="text"
name="FIRSTNAME" // Used for the action type
id="user-first-name"
value={formState.firstName} // formState from useReducer
required
onChange={(e) => {
const name = e.target.name;
const value = e.target.value;
dispatch({type: "CHANGE_" + name, payload: value });
}}
/>
You could have a separate action type within the reducer function for each DOM input such as...
switch (type) {
case CHANGE_FIRSTNAME:
// Return modified state.
case CHANGE_LASTNAME:
// Return modified state.
case CHANGE_STREET:
// Return modified state.
default:
return state;
}
This is inefficient however.
Efficient
The solution to this inefficiency is to abstract outwards in the reducer function.
Given this onChange
handler...
// For example, the DOM input attribute name is 'firstName'
onChange={(e) => {
const field = e.target.name;
const value = e.target.value;
dispatch({
type: "CHANGE_INPUT",
payload: {
value,
field,
},
});
}}
...the reducer function could contain this...
function formReducer(state, action) {
const { type, payload } = action;
switch (type) {
case "CHANGE_INPUT":
return { ...state, [payload.field]: payload.value };
default:
return state;
}
}
Normally one would have more cases in the reducer function but this example is simplified for educational purposes
In the code above, a computed property name is used to take the attribute name of the element ('firstName') and update state in the right place. In this case...
const initState = {
firstName: "Whatever was type in by user",
// Rest of state properties...
}
Gotchas
Remember how to access the data needed using computed property names. You need to wrap the dot notation object accessor for the action payload object in brackets.
return { ...state, [payload.field]: payload.value };
Further Cleaning
Optimization of code length can be achieved by moving code from the onChange()
handler to its own function. It might even be more descriptive to change the name to something like updateStateWithInputValue
.
const changeDOMInput = (e) => {
const field = e.target.name;
const value = e.target.value;
dispatch({
type: "CHANGE_INPUT",
payload: {
value,
field,
},
});
};
onChange={(e) => {
changeDOMInput(e);
}}
I hope this helps!
Top comments (3)
Which is the heavy operation we are trying to avoid and how does one solution handle it better than the other? 😵💫
I wanted to avoid having more actions than was necessary. The first solution was an action for each input and the other a single action to handle them all. The second solution is better because it make the code easier to read and follow. If there's a problem with the logic, I'd just have to look under a single action to debug.
By heavy do you mean time/space complexity? If so, I couldn't really say, but what I did was geared more towards readability.