React/ReactJS: useState v/s useReducer
useState
is one of the three basic Hooks introduced in React 16.8. We already
have covered it in our
previous introductory tutorial on
Hooks.
It provides the provision to implement states on erstwhile "stateless" or functional components, without converting them to class components.
To recap it a bit, we bring back the
<Circle/> function component from
our previous tutorial, but modifying the state to an object with the
property radius. The
useState declaration returns a pair of
items: a state and a function to update it. Below, the state is
circle and
setRadius() is the function to update it.
import React, { useState } from 'react';
const Circle = () => {
const [circle, setRadius] = useState({radius: 1});
return (
<div>
<p>The circumference of the circle of radius {circle.radius} is {2*Math.PI*circle.radius}</p>
<input type='text' onChange={(el) => setRadius({radius: el.target.value})}/>
</div>
);
}
export default Circle;
The above component renders as follows.
On entering some value to the input, the
setRadius() function updates the state
with it. This is how we use states in a functional component without
converting them to a class.
Now there is also another additional Hook called useReducer which also handles state.
The previous Hook useState is built over
useReducer. The difference is that,
updating a state via the
useReducer
involves dispatching an action to a reducer function which are mapped
to state transitions. This is very much akin to
Reducers
in Redux, which in turn is borowed from JavaScript's
Array.reduce()
method. Below we show its use with an example.
We first import useReducer.
import React, { useReducer } from 'react';
We create a functional component called
<Colour/> and call the
useReducer Hook inside it.
import React, { useReducer } from 'react';
const Colour = () => {
const [state, dispatch] = useReducer(reducer, '#ee82ee');
return (
<div>
...
</div>
)
}
export default Colour;
The reducer is a function which accepts
two arguments state and
action and returns the updated
state which is paired with the
dispatch method. The next argument is
#ee82ee, which is the initial state value.
Next we define the reducer for our
example.
const reducer = (state, action) => {
switch (action) {
case 'red':
return '#ff0000';
case 'yellow':
return '#ffff00';
default:
throw new Error()
}
}
The dispatch function dispatches a value for the action in the
reducer. Here is the full code of the
<Colour/> component with the
useReducer.
import React, { useReducer } from 'react';
const reducer = (state, action) => {
switch (action) {
case 'red':
return '#ff0000';
case 'yellow':
return '#ffff00';
default:
throw new Error()
}
}
const Colour = () => {
const [state, dispatch] = useReducer(reducer, '#ee82ee');
return (
<div>
<span style={{color: state}}>♥</span>
<button onClick={() => dispatch('red')}>RED</button>
<button onClick={() => dispatch('yellow')}>YELLOW</button>
</div>
)
}
export default Colour;
On click of either of the 'RED' or 'YELLOW' button, the dispatch function dispatches an action to the reducer, which in turn updates the state with a hex colour code.
Now let us operate on objects. We pick the two arguments passed to the
reducer - state and action - as objects. The state object has the
heart and
club properties while the action object
has the property type.
We redefine the reducer this way.
const initialState = {
heart: '#ee82ee',
club: '#ee82ee',
};
const reducer = (state, action) => {
switch (action.type) {
case 'red':
return { ...state, heart: '#ff0000' };
case 'yellow':
return { ...state, heart: '#ffff00' };
case 'green':
return { ...state, club: '#00ff00' };
case 'blue':
return { ...state, club: '#0000ff' };
default:
return state;
}
};
And the component <Colour/> using
the useReducer dispatching action objects
looks as follows.
import React, { useReducer } from "react";
const initialState = {
heart: '#ee82ee',
club: '#ee82ee',
};
const reducer = (state, action) => {
switch (action.type) {
case 'red':
return { ...state, heart: '#ff0000' };
case 'yellow':
return { ...state, heart: '#ffff00' };
case 'green':
return { ...state, club: '#00ff00' };
case 'blue':
return { ...state, club: '#0000ff' };
default:
return state;
}
};
const Colour = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<div>
<span style={{color: state.heart}}>♥</span>
<button onClick={() => dispatch({ type: 'red' })}>RED</button>
<button onClick={() => dispatch({ type: 'yellow' })}>YELLOW</button>
</div>
<div>
<span style={{color: state.club}}>♣</span>
<button onClick={() => dispatch({ type: 'green' })}>GREEN</button>
<button onClick={() => dispatch({ type: 'blue' })}>BLUE</button>
</div>
</div>
);
};
export default Colour;
Upon render, the component looks as follows.
Both the ♥ and ♣ entities are coloured with the respective initial
state values passed as #ee82ee and
#ee82ee.
As reminded earlier, useState is built on
top of useReducer. But performance related
comparisons of the two are irrelevant. However, managing related
states are always better with
useReducer as it gives you more control.
To conclude, use useState when dealing
with simple unrelated states. Use
useReducer when you have to use complex
states and where one state rely on the value of another and you have
to update them according to actions.