Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The returned setter function is changed in each render #61

Open
whitelizard opened this issue Nov 13, 2021 · 3 comments
Open

The returned setter function is changed in each render #61

whitelizard opened this issue Nov 13, 2021 · 3 comments

Comments

@whitelizard
Copy link

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
Copy link
Author

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.

@mattbrandlysonos
Copy link

mattbrandlysonos commented May 3, 2022

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:

const persistentSetState = useCallback(
(newState) => {
const newStateValue =
typeof newState === 'function' ? newState(state) : newState;
// persist to localStorage
set(key, newStateValue);
setState(newStateValue);
// inform all of the other instances in this tab
globalState.current.emit(newState);
},
[state, set, key]
);

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

@mateuspiresl
Copy link

mateuspiresl commented Jul 10, 2023

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:

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

const storage = createStorage(provider);

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 */)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants