use-persisted-state icon indicating copy to clipboard operation
use-persisted-state copied to clipboard

The returned setter function is changed in each render

Open whitelizard opened this issue 3 years ago • 3 comments

The setter function that is returned is not memoized, because it is always changed into a new function at each render (every invoke of the hook).

whitelizard avatar Nov 13 '21 17:11 whitelizard

Ok, I see in the code now that it is memoized and updates with the state. I guess it has to be that way. It is however a difference from how useState works, which never changes the setter function.

whitelizard avatar Nov 13 '21 18:11 whitelizard

I ran into this too!

Ok, I see in the code now that it is memoized and updates with the state. I guess it has to be that way.

I don't think it has to be this way! Here are the lines that cause persistentSetState to update every time state changes:

https://github.com/donavon/use-persisted-state/blob/2a33963050b209f8cc27060a32109978414a4dc7/src/usePersistedState.js#L30-L44

But it could be written like so:

const persistentSetState = useCallback(
  (newState) => {
    setState((oldState) => {
      const newStateValue = typeof newState === "function" ? newState(oldState) : newState
      // persist to localStorage
      set(key, newStateValue)

      // inform all of the other instances in this tab
      globalState.current.emit(newState)
      return newStateValue
    })
  },
  [set, key]
)

By getting oldState from the setState call itself, the callback no longer relies on state.

I believe this change would also fix #55

mattbrandlysonos avatar May 03 '22 15:05 mattbrandlysonos

In my use case, the reason for that is the key being dynamically generated. For that to work, the createPersistedState must be inside the React code.

function useMyHook({ id }) {
  const useMyState = createPersistedState(`my-${id}`)
  const [myState, setMyState] = useMyState()
  
  // ...
}

Accordingly to the library source code, the persistentSetState callback is updated every time the set param changes:

https://github.com/donavon/use-persisted-state/blob/f10ad4f08966cc1a3d0944d6aaf8b29b3e9a61d7/src/usePersistedState.js#L43

And this param changes every time the createPersistedState is called, since the storage is created on every call:

https://github.com/donavon/use-persisted-state/blob/f10ad4f08966cc1a3d0944d6aaf8b29b3e9a61d7/src/index.js#L26

Therefore, the setter changes on every render.


Workaround

function useMyHook({ id }) {
  const useMyState = createPersistedState(`my-${id}`)
  const [myState, setMyState] = useMyState()
  const setMyStateRef = useRef()
  setMyStateRef.current = setMyState
  
  // To set the state, setMyStateRef must be used instead of setMyState:
  setMyStateRef.current(/* new value */)
}

mateuspiresl avatar Jul 10 '23 15:07 mateuspiresl