Search code examples
reactjsreact-hooksreact-custom-hooks

REACT useLocalStorage custom hook is difficult to understand


It is difficult for me to understand this custom React hook. The code works, but I understand only some parts. It is a useLocalStorage hook. I will be grateful for any feedback.

This is my App.js file.

import "./App.css";

import useLocalStorage from "./useLocalStorage";

function App() {
  const [name, setName] = useLocalStorage("NAME", "");

  return (
    <>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <div>My name is {name}</div>
    </>
  );
}

export default App;

This is my useLocalStorage.js file.

import { useEffect, useState } from "react";

function getSavedValue(key, initialValue) {
  const savedValue = JSON.parse(localStorage.getItem(key));

  if (savedValue) return savedValue;

  return initialValue;
}

function useLocalStorage(key, initialValue) {

console.log(key);
console.log(initialValue);

  const [value, setValue] = useState(() => {
    return getSavedValue(key, initialValue);
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  });

  return [value, setValue];
}

export default useLocalStorage;

I understand how the savedValue (in useLocalStorage.js) is passed to the App.js file. I just retrieve it with the getSavedValue function, like this.

 

const [value, setValue] = useState(() => {
    return getSavedValue(key, initialValue);
  });

And later, I pass it to the App.js with these two lines.

Firstly, I return the value in the useLocalStorage.js

return [value, setValue];

Secondly, I pass the value variable into the name variable in the App.js file.

const [name, setName] = useLocalStorage("NAME", "");

This is how the passing of the savedValue from useLocalStorage.js to App.js works. I think.

But how does it work the other way around? How is the updated name variable passed from App.js to useLocalStorage? It seems to me that only “NAME” and initialValue are passed from App.js to useLocalStorage.js. Like this.

const [name, setName] = useLocalStorage("NAME", "");

As a result, in the useLocalStorage.js, I am able to access the “NAME” and intitialValue(“”) with the console.log statement.

console.log(key);
console.log(initialValue);

And it works. But I do not have this option for the updated name variable. Or do I? How do I access the updated name variable (available in the App.js) in the useLocalStorage.js???


Solution

  • To your first question ("How does the updated name variable get passed from App.js to useLocalStorage"): The value returned by useLocalStorage is the same as the useState it uses internally. In other words, your current setup is maintaining state in two places: in the useState (internal to useLocalStorage) and in local storage itself.

    To your second question ("Do I have access to the updated name variable"): Yes. Your name variable is value.

    While your code may have the intended effect, I don't think it works the way you think it does. Here's what will happen on the first render (assuming the key hasn't been set in localStorage yet):

    1. Your App component will call useLocalStorage with "NAME" and "" as parameters.
    2. Your hook will call useState. It is passing a function that is only executed once (on first render). After that, this function is never called again. In this function, you call getSavedValue. This will try to get the key from local storage and return it if it exists. If not, it will return the initial value.
    3. Finally, you return the value (which now is an empty string, the initialValue) and a setter.
    4. Your App.js uses the value and setValue (which it calls name and setName) to populate your form.
    5. At some point in the future, your useEffect hook runs and saves the previously-returned initial value to local storage. Now, the key should be set

    Now, you start typing in the input. Here's what will happen:

    1. The setName (which recall is actually setValue) is called. This simply updates the internal state of the localStorage.
    2. That update will cause a re-render of the App component.
    3. Your useLocalStorage hook will get called again. This time, it won't call the getSavedValue function. It will return whatever was set when you called setName/setValue in step 1.
    4. At some point in the future, your useEffect hook fires again in useLocalStorage, which will save the value to local storage.

    In a nutshell, you are using localStorage as an out-of-band storage mechanism that is updated independently of the actual component state. If you refresh the page, it will initialize from the local storage (instead of using the initialValue you pass in).