Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save to localStorage from reducer hook

Tags:

reactjs

I'm using the useReducer hook to save some global state. Because I would like to save some settings when the browser is closed, I save those settings to local storage.

At the moment I use dispatch to save the setting and a separate function to save this to local storage, but it would be nice if the setting is automatically saved after dispatch. (sometimes I forget to save to local storage and have a difference between state/local storage)

Reading the state from local storage is not a problem. I use the initialState parameter in the useReducer hook for this.

I think the answer is to not do it, but what is the alternative? (without using redux)

like image 625
roeland Avatar asked Jan 24 '19 12:01

roeland


1 Answers

Edit: inspired by this nice concise approach to locally persisting a useState hook, this is a better approach to locally persisting a useReducer hook (untested, but should be roughly correct):

import { useReducer, useEffect } from 'react'

function useLocallyPersistedReducer(reducer, defaultState, storageKey, init = null) {
  const hookVars = useReducer(reducer, defaultState, (defaultState) => {
    const persisted = JSON.parse(localStorage.getItem(storageKey))
    return persisted !== null
      ? persisted
      : init !== null ? init(defaultState) : defaultState
  })

  useEffect(() => {
    localStorage.setItem(storageKey, JSON.stringify(hookVars[0]))
  }, [storageKey, hookVars[0]])

  return hookVars
}

This gets rid of the need for the extra ref, and adds support for the optional init function. More importantly, it also only updates local storage whenever the state is updated, rather than on every render.


I was just playing around with doing something similar, and wanted to try and achieve it using a custom hook that could be used in place of useReducer. Came up with the following rough hook (I did add an extra parameter so I can specify the key the data gets stored under):

// DO NOT USE THIS; see the updated implementation above instead
import { useRef, useReducer } from 'react'

function useLocallyPersistedReducer(reducer, defaultState, storageKey) {
  const isLoading = useRef(true)
  if (isLoading.current) {
    try {
      const persisted = JSON.parse(localStorage.getItem(storageKey))
      defaultState = persisted
    } catch (e) {
      console.warn(`Failed to load state '${storageKey}' from localStorage; using default state instead`)
    }
    isLoading.current = false
  }

  const [ state, dispatch ] = useReducer(reducer, defaultState)

  try {
    localStorage.setItem(storageKey, JSON.stringify(state))
  } catch (e) {
    console.warn(`Failed to store updated state '${storageKey}'`)
  }

  return [ state, dispatch ]
}

As a rough job, it works, but there are probably some nuances that I've missed. Hope that gives you an idea of how you could approach it though!

like image 51
zkcro Avatar answered Sep 18 '22 22:09

zkcro