user12465675
user12465675

Reputation:

React Hooks and localStorage dont set correct value

Hello I had an idea to make a hook to increase the font size and save preferences in localStorage basically I have a state that goes from 1 to 4, and then when I click the button add I add +1 to the state until I reach number 4 and on the remove button I remove 1 from the state until 1

But I have doubts on how to save this to my location basically if i don't use my useState just with getInitialValue It works normally.

like this gif, If I add the value manually it works:

enter image description here

but if I try to use my setFont I have problems (as it is saved in localStorage):

enter image description here

and i got this on localStorage :

enter image description here

code:

export default function App() {
  const { fontSize, setSize } = useFontSize();
  console.log(fontSize);
  return (
    <div className="App">
      <button
        onClick={() => {
          setSize(fontSize + 1);
        }}
      >
        add
      </button>
      <button
        onClick={() => {
          setSize(fontSize + 1);
        }}
      >
        remove
      </button>
    </div>
  );
}

hook:

export default function useFontSize(defaultSize = { size: 1 }) {
  const [fontSize, _setSize] = useState(getInitialSize);
  function getInitialSize() {
    const savedSize = localStorage.getItem('_size_acessibility_font');
    const parsedSize = JSON.parse(savedSize);
    if (parsedSize) {
      const { size } = parsedSize;
      if (size >= 1 && size <= 4) {
        return size;
      }
    } else {
      return defaultSize.size;
    }
  }

  useEffect(() => {
    console.log(fontSize, 'on useEffect to set on localStorage');
    localStorage.setItem(
      '_size_acessibility_font',
      JSON.stringify({ size: fontSize }),
    );
  }, [fontSize]);

  return {
    fontSize,
    setSize: ({ setSize, ...size }) => {
      console.log(size, 'on function set size');
      if (size > 4) {
        return _setSize(4);
      }
      if (size < 1) {
        return _setSize(1);
      }
      return _setSize(size);
    },
  };
}

example:

https://codesandbox.io/s/focused-newton-x0mqd

I don't know if this is the best logic for this context, if someone can help me.

Upvotes: 1

Views: 125

Answers (2)

ggorlen
ggorlen

Reputation: 56945

This seems a tad overengineered and upsets a few hooks idioms. For example, returning a named object pair for a hook is less typical than an array pair. The set function itself is complex and returns the result of the _setSize calls. Naming could be clearer if fontSize matched setSize by using setFontSize.

({ setSize, ...size }) is problematic since the caller is (correctly) providing an integer.

Here's a minimal, complete version that fixes these issues (local storage is mocked since Stack Snippets is sandboxed):

const localStorageMock = (() => {
  const storage = {};
  return {
    getItem: k => storage[k],
    setItem: (k, v) => {storage[k] = v.toString();}
  };
})();

const {useState, useEffect} = React;

const useFontSize = (defaultSize=1) => {
  const clamp = (n, lo=1, hi=4) => Math.min(hi, Math.max(n, lo));
  const clean = n => isNaN(n) ? defaultSize : clamp(+n);
  const storageName = "_size_acessibility_font";
  const fromStorage = clean(localStorageMock.getItem(storageName));
  const [fontSize, setFontSize] = useState(fromStorage);

  useEffect(() => {
    localStorageMock.setItem(storageName, fontSize);
  }, [fontSize]);

  return [fontSize, size => setFontSize(clean(size))];
};

const App = () => {
  const [fontSize, setFontSize] = useFontSize();
  return (
    <div>
      <div>Font size: {fontSize}</div>
      <button onClick={() => setFontSize(fontSize + 1)}>
        +
      </button>
      <button onClick={() => setFontSize(fontSize - 1)}>
        -
      </button>
    </div>
  );
};

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

Upvotes: 1

Jacob
Jacob

Reputation: 926

In useFontSize, you return

return {
  fontSize,
  setSize: ({ setSize, ...size }) => {
    console.log(size, 'on function set size');
    if (size > 4) {
      return _setSize(4);
    }
    if (size < 1) {
      return _setSize(1);
    }
    return _setSize(size);
  },
};

However, in App, you call setSize with just a number setSize(fontSize + 1); when it is expecting an object.

If you change useFontSize to return

return {
  fontSize,
  setSize: (size) => {
    console.log(size, 'on function set size');
    if (size > 4) {
      return _setSize(4);
    }
    if (size < 1) {
      return _setSize(1);
    }
    return _setSize(size);
  },
};

It should work.

Note, you will want to clear your current local storage, or add some error checking.

Also note, although it is just an example, both add and remove use fontSize + 1

Upvotes: 0

Related Questions