I am learning about custom hooks and useReducer in React.js. Here is an example (sandbox) to illustrate useReducer adapted from the course Modern React with Redux:
import "./styles.css";
import { useReducer } from "react";
const INCREMENT_COUNT = "increment";
const SET_VALUE_TO_ADD = "change_value_to_add";
const DECREMENT_COUNT = "decrement";
const ADD_VALUE_TO_COUNT = "add_value_to_count";
const reducer = (state, action) => {
switch (action.type) {
case INCREMENT_COUNT:
return {
...state,
count: state.count + 1
};
case DECREMENT_COUNT:
return {
...state,
count: state.count - 1
};
case ADD_VALUE_TO_COUNT:
return {
...state,
count: state.count + state.valueToAdd,
valueToAdd: 0
};
case SET_VALUE_TO_ADD:
return {
...state,
valueToAdd: action.payload
};
default:
return state;
}
};
function CounterPage({ initialCount }) {
const [state, dispatch] = useReducer(reducer, {
count: initialCount,
valueToAdd: 0
});
console.log(state);
const increment = () => {
dispatch({
type: INCREMENT_COUNT
});
};
const decrement = () => {
dispatch({
type: DECREMENT_COUNT
});
};
const handleChange = (event) => {
const value = parseInt(event.target.value) || 0;
dispatch({
type: SET_VALUE_TO_ADD,
payload: value
});
};
const handleSubmit = (event) => {
event.preventDefault();
dispatch({
type: ADD_VALUE_TO_COUNT
});
};
return (
<>
<h1>Count is {state.count}</h1>
<div>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
<form onSubmit={handleSubmit}>
<label>Add a lot!</label>
<input
value={state.valueToAdd || ""}
onChange={handleChange}
type="number"
/>
<button>Add it!</button>
</form>
</>
);
}
export default function App() {
return (
<div className="App">
<CounterPage initialCount={10} />
</div>
);
}
It seems to me that this example can be implemented simpler (sandbox) with a custom hook:
import { useState } from "react";
import "./styles.css";
const useCounter = (initialCount) => {
const [count, setCount] = useState(initialCount);
const [valueToAdd, setValueToAdd] = useState(0);
const incrementCount = () => setCount(count + 1);
const decrementCount = () => setCount(count - 1);
const addValueToCount = () => setCount(count + valueToAdd);
return {
count,
incrementCount,
decrementCount,
addValueToCount,
valueToAdd,
setValueToAdd
};
};
function CounterPage({ initialCount }) {
const state = useCounter(initialCount);
const handleChange = (event) => {
const value = parseInt(event.target.value) || 0;
state.setValueToAdd(value);
};
const handleSubmit = (event) => {
event.preventDefault();
state.addValueToCount();
};
console.log("render");
return (
<>
<h1>Count is {state.count}</h1>
<div>
<button onClick={state.incrementCount}>Increment</button>
<button onClick={state.decrementCount}>Decrement</button>
</div>
<form onSubmit={handleSubmit}>
<label>Add a lot!</label>
<input
value={state.valueToAdd || ""}
onChange={handleChange}
type="number"
/>
<button>Add it!</button>
</form>
</>
);
}
export default function App() {
return (
<div className="App">
<CounterPage initialCount={10} />
</div>
);
}
I actually like how the custom hook organizes changing state into several functions instead of clumping everything into a single reducer. This reminds me of methods in OOP.
Why is useReducer preferable? If it's not preferable in this example, how can one modify the example to illustrate the benefits of using useReducer as opposed to using a custom hook?