Conversation

alnorth

At the moment if a component that uses useLocalStorage rerenders then useLocalStorage will call JSON.parse(store) as a result of the rerender. The value of store won't have changed, but the new call to JSON.parse will mean that a new value is returned with identical contents to the old value. Any hooks then using that value in their deps array will then have to be run again, even though the actual data is the same.

I've updated useLocalStorage so that it uses useMemo to avoid repeated calls to JSON.parse with the same store value.

@joshxyzhimself

image

our current awful workaround

hope this pr gets merged

@RobertRad

We ran into the same problem - somewhat similar with a JWT, on which a some other hooks depend.

I think data in the localStorage might be some basic values (authorization, language) which is used (directly or derived) in a lot of parts of the application. So this trade-off between memory & CPU should be worth using the useMemo().

@joshxyzhimself

update on my workaround, no issues so far.

/**
 *
 * If active tab updates localStorage:
 *  - At current tab, localStorage is updated.
 *  - At current tab, "storage" event is emitted.
 *  - At current tab, window receives "storage" event.
 *  - At current tab, state is updated.
 *
 * If inactive tab updates localStorage:
 *  - At inactive tab, localStorage is updated.
 *  - At current tab, window receives "storage" event.
 *  - At current tab, state is updated.
 *
 */

import { useCallback, useEffect, useMemo, useState } from "react";

export function useLocalStorage<T>(key: string) {
  const [state, set_state] = useState<T | null>(() => {
    const unparsed = localStorage.getItem(key);
    if (typeof unparsed === "string") {
      const parsed = JSON.parse(unparsed) as T;
      return parsed;
    }
    return null;
  });

  useEffect(() => {
    const listener = (e: StorageEvent) => {
      if (e.key === key) {
        if (typeof e.newValue === "string") {
          const parsed = JSON.parse(e.newValue) as T;
          set_state(parsed);
        }
      }
    };
    window.addEventListener("storage", listener);
    return () => {
      window.removeEventListener("storage", listener);
    };
  }, [key]);

  /**
   * @description dises an event so all instances gets updated.
   */
  const set_stored = useCallback(
    (value: T | null) => {
      const newValue = JSON.stringify(value);
      localStorage.setItem(key, newValue);
      const event = new StorageEvent("storage", { key, newValue });
      disEvent(event);
    },
    [key],
  );

  return useMemo(
    () => [state, set_stored] as [T | null, (value: T | null) => void],
    [state, set_stored],
  );
}

export default useLocalStorage;
import useLocalStorage from "./useLocalStorage";

export interface Session {
  id: string;
  iss: string;
  aud: string;
  sub: string;
  iat: number;
  nbf: number;
  exp: number;
}

export const useSession = () => {
  return useLocalStorage<Session>("session");
};

export default useSession;

@pedroapfilho

This hook should accept an initialValue being passed.

@yunyu

@lynnandtonic @tylermcginnis Any chance we can get this merged?

@RutsuKun

Is this package unmaintained?

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

Successfully merging this pull request may close these issues.