React: understanding useReducer() & custom hooks
Tags: react, javascript, webdev, tutorial
Published: 2022-10-28 11:37:25

In React, `useState()` is the main brick 🧱 for building... pretty much everything.

Sure, we have contexts and memoization and references but the **core** are these guys. 

```js
const [item, setItem] = React.useState()
```

The thing is, in real-world code we end up with several of these lego blocks working together. 

Fake - not that fake - example out of context:

```ts
const [item, setItem] = React.useState<PlaylistItem | undefined>()
const [isPlaying, setIsPlaying] = React.useState(false);
const [itemFocus, setItemFocus] = React.useState<'plpause' | 'info-show' | 'info-hide'>('plpause');
const [time, setTime] = React.useState(0);
const [isShowingMoreInfo, setIsShowingMoreInfo] = React.useState(false);
```

***

One tool in our toolboxes 🧰 for extracting state logic is [`useReducer()`](https://reactjs.org/docs/hooks-reference.html#usereducer).

In our - out of context - example we might want to refactor the state management. (Making easier to test this logic isolated or even share the logic with other components)

Let's say we define `playlistState` as the group of our state variables:
{ `item` + `isPlaying` + `time` + ... } 

`useReducer()` collapses both the state and the update function:

```js
const [ playlistState, dispatchPlaylistUpdate ] = useReducer(
  playlistReducer, 
  initialPlaylistState
)
```

Now, all our update functions are replaced by the _one-to-rule-them-all_ function (convention is to include "dispatch" in its naming):

```diff
- setItem(item)
+ dispatchPlaylistUpdate({ type: 'set-item', item })
```

```diff
- setIsPlaying(isPlaying)
+ dispatchPlaylistUpdate({ type: 'is-playing', isPlaying })
```

```diff
- setTime(time)
+ dispatchPlaylistUpdate({ type: 'set-time', time })
```

...etc.
 
> Side note 🗒️: At this point, I'd say it's easier to detect redundant states if we realize that calls to `dispatchPlaylistUpdate({ })` are updating always the same state vars at the same time.

***

Now, I feel the urge to _**name the hook**_:

```diff
- const [ playlistState, dispatchPlaylistUpdate ] = useReducer(
-   playlistReducer, 
-   initialPlaylistState
- )
+ const { playlistState, dispatchPlaylistUpdate } = usePlaylist()
```

***

And here we are, _on the very slopes of Mount Doom_ 🌋, if we _**name**_ each update call, using meaningful names...

```diff
- dispatchPlaylistUpdate({ type: 'set-item', item })
+ changeTrack(item)
```

```diff
- dispatchPlaylistUpdate({ type: 'is-playing', isPlaying })
+ startTrack()
+ stopTrack()
```

```diff
- dispatchPlaylistUpdate({ type: 'set-time', time })
+ updateTrackProgress(time)
```

=> 

```diff
- const { playlistState, dispatchPlaylistUpdate } = usePlaylist()
+ const { 
+  playlist, 
+  changeTrack, 
+  startTrack, 
+  stopTrack, 
+  updateTrackProgress
+ } = usePlaylist()
```

We got a - IMO - more readable code.

***

In the end, these two codes are interchangeable, **we've just refactored the thing**:

```js
const [item, setItem] = ...
const [isPlaying, setIsPlaying] = ...
const [time, setTime] = ...
```

===

```js
const { 
  playlist, 
  changeTrack, 
  startTrack, 
  stopTrack, 
  updateTrackProgress
} = usePlaylist()
```

> Note that I could have jumped right to the final refactor in this post, but I was trying to share my mental-model, my '_eureka_' for understanding custom hooks. 

--

> Banner image by [Storyset](https://storyset.com/imagination)

Thanks for reading 💚.