useState vs useReducer: What are they and when to use them?
Tags: react, javascript, tutorial, webdev
Published: 2022-02-19 13:17:57

It is common to see useState hook used for state management, However React also have another hook to manage component's state, Which is useReducer hook. In fact, useState is built on useReducer!. So a question arises: What's the difference between the two ? And when should you use either ?

## useState hook:

useState hook is a hook used to manipulate and update a functional component. The hook takes one argument which is the initial value of a state and returns a state variable and a function to update it.

```javascript
const [state, setState] = useState(initialValue)
```

So a counter app using the useState hook will look like this: 

```javascript
function Counter() {
  const initialCount = 0
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}
```



## useReducer hook:
this hook is similar to the `useState` hook. However it's able to handle more complex logic regarding the state updates. It takes two arguments: a reducer function and an initial state. The hook then returns the current state of the component and a dispatch function

```javascript
const [state, dispatch] = useReducer(reducer, initialState)
```

the `dispatch` function is a function that pass an `action` to the `reducer` function.

The `reducer` function generally looks like this:

```javascript
const reducer = (state, action) => {
	switch(action.type) {
		case "CASE1": 
			return "new state";
		case "CASE2": 
			return "new state";
		default:
			return state
	}
}
```

The action is usually an object that looks like this: 

```javascript
// action object:
{type: "CASE1", payload: data}
```

The `type` property tells the reducer what type of action has happened ( for example: user click on 'Increment' button). The `reducer` function then will determine how to update the `state` based on the `action`.

So a counter app using the useReducer hook will look like this: 

```javascript
const initialCount = 0

const reducer = (state, action) => {
	switch (action.type) {
		case "increment":
			return action.payload;
		
		case "decrement": 
			return action.payload;
			
		case "reset": 
			return action.payload;
			
		default: 
			return state;
	}
}

function Counter() {
	const [count, dispatch] = useReducer(reducer, initialCount)
	
	return (
    <>
      Count: {count}
      <button onClick={() => dispatch({type: "reset", payload: initialCount}))}>Reset</button>
      <button onClick={() => dispatch({type: "decrement", payload: state - 1})}>Decrement</button>
      <button onClick={() => dispatch({type: "increment", payload: state + 1})}>Increment</button>
    </>
  );
}
```

## When should i useReducer() ?

As stated above, The useReducer hook handles more complex logic regarding the state updates. So if you're state is a single `boolean`, `number`, or `string`, Then it's obvious to use useState hook. However if your state is an object (example: person's information) or an array (example: array of products ) useReducer will be more appropriate to use. 

### Let's take an example of fetching data:

If we have a state that represent the data we fetched from an API, The state will either be one of this three 'states': `loading`, `data`, or `error`

When we fetch from an API, Our state will go from `loading` ( waiting to receive data), to either `data` or we'll get an `error`

Let's compare how we handle state with the useState hook and with the useReducer hook

- **With the useState hook:**


```javascript
function Fetcher() {
	const [loading, setLoading] = useState(true)
	const [data, setData] = useState(null)
	const [error, setError] = useState(false)
	
	useEffect(() => {
		fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => {
			setLoading(false)
			setData(res.data)
			setError(false)
		}).catch((err) => {
			setLoading(false)
			setData(null)
			setError(true)
		})
		,[])
		
		return (
		{loading ? <p>Loading...</p> 
		 : <div>
			<h1>{data.title}</h1>
			<p>{data.body}</p>
		 </div> }
		{error && <p>"An error occured"</p> }
		)
		
}
```


- **With the useReducer hook:**

```javascript

const initialState = {
	loading: true,
	data: null,
	error: false
}

const reducer = (state, action) => {
	switch (action.type) {
		case "SUCCESS":
			return {
				loading: false,
				data: action.payload,
				error: false
			};
			
		case "ERROR": 
			return {
				loading: false,
				data: null,
				error: true
			};
			
		default:
			return state;
	}
}

function Fetcher() {
	const [state, dispatch] = useReducer(reducer, initialState)
	
	useEffect(() => {
	fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => {
		dispatch({type: "SUCCESS", payload: res.data})
	}).catch(err => {
		dispatch({type: "ERROR"})
	})
	
	} ,[])
	
	return (
		{state.loading ? <p>Loading...</p> 
		 : <div>
			<h1>{state.data.title}</h1>
			<p>{state.data.body}</p>
		 </div> }
		{state.error && <p>"An error occured"</p> }
		)
	
}
```

As you can see with the useReducer hook we've grouped the three states together and we also updated them together. useReducer hook is **extremely useful** when you have states that are related to each other, Trying to handle them all with the useState hook may introduce difficulties depending on the complexity and the bussiness logic of it.

## Conclusion

To put it simply: if you have a single state either of a `boolean`, `number`, or `string` use the useState hook. And if you're state is an object or an array, Use the useReducer hook.  Especially if it contains states related to each other.

I hope this post was helpful, Happy coding!