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

Add useMemo call to useLocalStorage so that the value returned only changes if store actually changes. #304

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

alnorth
Copy link

@alnorth alnorth commented May 15, 2024

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

image

our current awful workaround

hope this pr gets merged

@RobertRad
Copy link

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

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 dispatches 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 });
      dispatchEvent(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;

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

Successfully merging this pull request may close these issues.

3 participants