From ccf168cadd58c3047e1a6ce178d415337cbd140b Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 28 May 2025 12:32:45 -0400 Subject: [PATCH] Make doubly sure that useLocalStorage reacts to key changes I think my previous commit technically made sure that the value would converge to the right thing eventually, but it could become temporarily out of sync with the key passed to the hook, at least. The test demonstrates how. We haven't yet triggered this failure mode in practice, I think; this is more of a theoretical correctness thing. --- src/useLocalStorage.test.tsx | 27 ++++++++++++++++++++++++++- src/useLocalStorage.ts | 8 +++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/useLocalStorage.test.tsx b/src/useLocalStorage.test.tsx index 4b0a058d..8cb66b77 100644 --- a/src/useLocalStorage.test.tsx +++ b/src/useLocalStorage.test.tsx @@ -7,7 +7,8 @@ Please see LICENSE in the repository root for full details. import { test } from "vitest"; import { render, screen } from "@testing-library/react"; -import { type FC, useEffect } from "react"; +import { type FC, useEffect, useState } from "react"; +import userEvent from "@testing-library/user-event"; import { setLocalStorageItem, useLocalStorage } from "./useLocalStorage"; @@ -21,3 +22,27 @@ test("useLocalStorage reacts to changes made by an effect mounted on the same re render(); screen.getByText("Hello!"); }); + +test("useLocalStorage reacts to key changes", async () => { + localStorage.clear(); + localStorage.setItem("value-1", "1"); + localStorage.setItem("value-2", "2"); + + const Test: FC = () => { + const [key, setKey] = useState("value-1"); + const [value] = useLocalStorage(key); + if (key !== `value-${value}`) throw new Error("Value is out of sync"); + return ( + <> + +
Value is: {value}
+ + ); + }; + const user = userEvent.setup(); + render(); + + screen.getByText("Value is: 1"); + await user.click(screen.getByRole("button", { name: "Switch keys" })); + screen.getByText("Value is: 2"); +}); diff --git a/src/useLocalStorage.ts b/src/useLocalStorage.ts index 517c7c62..e96c9c87 100644 --- a/src/useLocalStorage.ts +++ b/src/useLocalStorage.ts @@ -6,9 +6,10 @@ Please see LICENSE in the repository root for full details. */ import EventEmitter from "events"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect } from "react"; import { useLatest } from "./useLatest"; +import { useReactiveState } from "./useReactiveState"; type LocalStorageItem = ReturnType; @@ -19,8 +20,9 @@ export const localStorageBus = new EventEmitter(); export const useLocalStorage = ( key: string, ): [LocalStorageItem, (value: string) => void] => { - const [value, setValue] = useState(() => - localStorage.getItem(key), + const [value, setValue] = useReactiveState( + () => localStorage.getItem(key), + [key], ); const latestValue = useLatest(value);