rehooks/local-storage

Hook does not re-initialise when key is not a constant

atiredturtle opened this issue · 4 comments

Calling the useLocalStorage hook with a non-constant key causes errors.

This is an issue in the case someone wishes to make a custom hook that is built on top of useLocalStorage.

Example
This is a bit contrived, and one would probably not write code like this, but it's the simplest way I can demonstrate the bug.

In this example, we have a custom hook using useLocalStorage with a parameter name as input. We have some asyncAge parameter that we know is resolved when asyncAge.pending is true.

const useAgeEdit = (name, asyncAge) => {
  const [age, setAge] = useLocalStorage(name, 0);

  // A use effect hook that will set the age based on the asyncAge value passed in
  useEffect(() => {
    if (!asyncAge.pending && !age) {
      setAge(asyncAge.age)
    }
  }, [asyncAge])

  return [age, setAge]
}

This hook does not work currently, as useLocalStorage does not change when the params in change. The only way around this currently is to forcefully re-render a component so that the hook initialises.

Hello, does this look like a valid test case?
https://github.com/rehooks/local-storage/blob/issue/38/test/index.test.tsx#L192-L216

I am not sure if I am reproducing the problem correctly. To me this seems ok (apart from the react-testing-library complaining about calling waitForNextUpdate within an act)

Not quite. The issue I've found seems to be on the hook not updating the age and setAge variables when we create a new instance of it.

Here's an example I created for myself to test it

import React, {useState} from "react";
import { useLocalStorage } from "../src/index";

// key prop needs to change
const EditAge = ({name}) => {
  let [age, setAge] = useLocalStorage(name, 0);
  return (
    <div>
      <h1>Currently Editing {name}'s Age</h1>
      <input type="number" value={age} onChange={event => setAge(parseInt(event.target.value) || age)}/>
    </div>
  )
}

export default () => {
  const allPeople = ["Tim", "Bob", "Gemma"]
  const [editPerson, setEditPerson] = useState(undefined);

  return (
    <div>
      <EditAge name={editPerson}/>
      {allPeople.map(name => <button onClick={() => setEditPerson(name)} key={name}>{name}</button>)}
    </div>
  );
}

If you run it, you'll notice that if you edit an age, and then click on a different name, the age stays the same (since the hook doesn't update on props change)

Seems like it has to do with passing 0 as the default value.