Both hooks define an initial state and return a setter and a getter to handle the state:
[value,fn] = useReducer(...); // or useState(...);
But, in the above code snippet, the behavior of fn
differs from useReducer
to useState
. Also, useReducer
needs 2 parameters while useState
needs only 1. Basically:
useReducer
: expect 2 parameters, (a) a function (known as the reducer function), and (b) the initial value for the state. The reducer function will be called to modify the state whenever you call fn
with some value. And the value passed to fn
will be passed through to the reducer function (as the second parameter).
useState
: expect only 1 parameter, the initial value for the state. It will just replace the state with the value passed as an argument to fn
(optionally, you can also pass a function to the fn
, that receives the current state as a parameter and returns the new state).
Example of useState
:
const { useState } = React;
const App = () => {
const initialState = { called: false, name: 'Jane' };
const [value, setValue] = useState(initialState);
const replaceState = () => {
setValue({
called: true,
fullName: `${value.name ? value.name : 'Just'} Fonda`
});
}
return (
<div className="App">
<button onClick={replaceState}>Replace Name</button>
<button onClick={(e) => setValue(initialState)}>Reset</button>
<pre>{JSON.stringify(value)}</pre>
{value.called
? <pre>Notice that there's no 'name' attribute anymore</pre>
: null}
</div>
);
}
ReactDOM.render(<App />,root);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Example of useReducer
:
You must build a function (the reducer
) that will modify the state. That function must receive 2 parameters: the actual state and any value you need to modify the state.
Also, this function must return a value that React will set as the new state.
If you have ever used the redux pattern, this behavior must be familiar to you. So, if you, like everyone, want to stick to the redux pattern, use action
as the name of the second parameter of the reducer function (that will be used to modify the state). It'll be an object containing 2 attributes: type
and payload
.
The logic inside the reducer process the action, by using the type
(string) to decide how to modify the state using the payload
(any). It's not uncommon to have a switch-case
inside the reducer. So, let's follow the tradition and build a reducer using this redux-like pattern.
const { useReducer } = React;
// Notice the two parameters of the reducer:
// state: current state
// payload: object with the shape {type: string, payload: any}
// Also, notice that the reducer MUST return a value to be used
// as the new state. Remember that if you return nothing
// javascript will return undefined
const reducer = (state, action) => {
switch(action.type) {
case 'inc': return state + action.payload;
case 'dec': return state - action.payload;
// for any other type, return the current state
default: return state;
}
}
const App = () => {
// specify the reducer and an initial value for the state
const [value, setValue] = useReducer(reducer, 0);
const inc = (e) => setValue({type: 'inc', payload: 1});
const dec = (e) => setValue({type: 'dec', payload: 1});
return (
<div className="App">
<button onClick={inc}>Increment</button>
<button onClick={dec}>Decrement</button>
Counter:{value}
</div>
);
}
ReactDOM.render(<App />,root);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
As a final side note, the setValue
name as I used on the snippet above is not even close to the most common name used for the function returned by useReducer
. It's much more common to call it dispatch
.