Persisting useReducer with a custom React Hook
Tags: react, redux, javascript, discuss
Published: 2021-04-28 13:45:14

---
title: Persisting useReducer with a custom React Hook
published: true
description: When I was building a new app I decided to use react hooks rather than Redux. Here's how it went and what I used in place of redux-persist.
tags: React, Redux, Javascript, Discuss
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihfm27dd0xu8f3idmsyq.jpg
---

*google "reducer" if you're confused by the cover image*

When I was building [gistmarks](https://gistmarks.io), I needed to store users data and pass it around to various parts of the app. For this I typically use Redux combined with Redux Persist. This approach is tried and true but does involve quite a bit of boilerplate code so I wanted to try something new.

I quickly learned that `useReducer` is a highly competent alternative to Redux and typing it (adding typescript types) is much more straightforward than it is with Redux. There was however one thing missing: Persistence.

For me, being able to persist a users state is crucial to my app functioning so having a way of persisting data with the `useReducer` hook was essential. With Redux I would always use [redux-persist](https://github.com/rt2zz/redux-persist) however there didn't seem to be any formal way of doing it with `useReducer`. 

As a result I created my own hook that persists the reducers data to localStorage. Here's that hook:

Javascript Version:

```js
import { useEffect, useReducer } from "react"
import deepEqual from "fast-deep-equal/es6"
import { usePrevious } from "./usePrevious"

export function usePersistedReducer(
  reducer,
  initialState,
  storageKey,
) {
  const [state, dispatch] = useReducer(reducer, initialState, init)
  const prevState = usePrevious(state)

  function init() {
    const stringState = localStorage.getItem(storageKey)
    if (stringState) {
      try {
        return JSON.parse(stringState)
      } catch (error) {
        return initialState
      }
    } else {
      return initialState
    }
  }

  useEffect(() => {
    const stateEqual = deepEqual(prevState, state)
    if (!stateEqual) {
      const stringifiedState = JSON.stringify(state)
      localStorage.setItem(storageKey, stringifiedState)
    }
  }, [state])

  return { state, dispatch }
}

```

Typescript Version:

```js

import { useEffect, useReducer } from "react"
import deepEqual from "fast-deep-equal/es6"
import { usePrevious } from "./usePrevious"

export function usePersistedReducer<State, Action>(
  reducer: (state: State, action: Action) => State,
  initialState: State,
  storageKey: string
) {
  const [state, dispatch] = useReducer(reducer, initialState, init)
  const prevState = usePrevious(state)

  function init(): State {
    const stringState = localStorage.getItem(storageKey)
    if (stringState) {
      try {
        return JSON.parse(stringState)
      } catch (error) {
        return initialState
      }
    } else {
      return initialState
    }
  }

  useEffect(() => {
    const stateEqual = deepEqual(prevState, state)
    if (!stateEqual) {
      const stringifiedState = JSON.stringify(state)
      localStorage.setItem(storageKey, stringifiedState)
    }
  }, [state])

  return { state, dispatch }
}

```

For this hook you will also need a companion hook called `usePrevious`

Typescript Version:

```js
import { useRef, useEffect } from "react"

// Given any value
// This hook will return the previous value
// Whenever the current value changes

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function usePrevious(value: any) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}

```

Javascript Version:

```js
import { useRef, useEffect } from "react"

// Given any value
// This hook will return the previous value
// Whenever the current value changes

export function usePrevious(value) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  })
  return ref.current
}

```


## How it works

The hook manages syncing state internally. Whenever you issue a dispatch, an effect within the hook checks the previous state of the reducer and if the state changed, it will backup that state to localStorage.


## How to use it

Using this hook is super easy.

```js
const initialState = {...}

function reducer(state = initialState, action) {...}

const storageKey = 'MY_STORAGE_KEY'

const { state, dispatch } = usePersistedReducer(reducer, initialState, storageKey)

// use state and dispatch as you normally would.

```

## Conclusion

That's pretty much it. If you think I could improve this hook leave a comment and I'll update the article. If you liked this article check out some of my other posts [here](https://sunnygolovine.com/blog)