Skip to content

Commit

Permalink
feat(react): add useLocalStorage hook
Browse files Browse the repository at this point in the history
  • Loading branch information
hckhanh committed Mar 30, 2021
1 parent 50caa6a commit 182dbb8
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
101 changes: 101 additions & 0 deletions packages/trakas-react/__tests__/hooks/useLocalStorage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useLocalStorage } from "../../src/hooks/useLocalStorage";

type TestComponentProps<T> = {
initialValue?: unknown;
value: unknown;
};

function TestComponent<T>(props: TestComponentProps<T>) {
const [storeValue, setValue, clearValue] = useLocalStorage("item", props.initialValue);

const handleChangeValue = () => {
setValue(props.value);
};

return (
<>
<button aria-label="set" onClick={handleChangeValue}>
{JSON.stringify(storeValue)}
</button>
<button aria-label="clear" onClick={clearValue} />
</>
);
}

describe("useLocalStorage", () => {
test("set initial value for nonexistent item in Local Storage", async () => {
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("1");
expect(buttonElement).toBeInTheDocument();
});

test("return undefined for nonexistent item with no initial value", async () => {
render(<TestComponent value={2} />);

const buttonElement = screen.getByRole("button", { name: "set" });
expect(buttonElement).toHaveTextContent("");
});

test("set value after initializing", async () => {
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("1");
userEvent.click(buttonElement);

expect(buttonElement).toHaveTextContent("2");
expect(localStorage.getItem("item")).toBe("2");
});

test("load the value which is already stored in Local Storage", async () => {
localStorage.setItem("item", String(3));
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("3");
expect(buttonElement).toBeInTheDocument();
});

test("clear the value which is already stored in Local Storage", async () => {
localStorage.setItem("item", String(3));
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = screen.getByRole("button", { name: "clear" });
userEvent.click(buttonElement);

expect(localStorage.getItem("item")).toBeNull();
});

test("load the initial value when stored value is error", async () => {
localStorage.setItem("item", `{"a":1`);
render(<TestComponent initialValue={{ b: 2 }} value={{ c: 3 }} />);

const buttonElement = await screen.findByText(`{"b":2}`);
expect(buttonElement).toBeInTheDocument();
});

test("load the initial value when stored value is incompatible value", async () => {
localStorage.setItem("item", "NaN");
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("1");
expect(buttonElement).toBeInTheDocument();
});

test("load the initial value when stored value is null", async () => {
localStorage.setItem("item", "null");
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("1");
expect(buttonElement).toBeInTheDocument();
});

test("load the initial value when stored value is undefined", async () => {
localStorage.setItem("item", "undefined");
render(<TestComponent initialValue={1} value={2} />);

const buttonElement = await screen.findByText("1");
expect(buttonElement).toBeInTheDocument();
});
});
24 changes: 24 additions & 0 deletions packages/trakas-react/src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useCallback, useState } from "react";

export function useLocalStorage<T = undefined>(key: string, initialValue?: T): [T, (value: T) => void, () => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const valueInString = localStorage.getItem(key);
return valueInString ? JSON.parse(valueInString) ?? initialValue : initialValue;
} catch {
return initialValue;
}
});

const setValue = useCallback(
(value: T) => {
localStorage.setItem(key, JSON.stringify(value));
setStoredValue(value);
},
[key]
);

const clearValue = useCallback(() => localStorage.removeItem(key), [key]);

return [storedValue, setValue, clearValue];
}
1 change: 1 addition & 0 deletions packages/trakas-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./hooks/useToggle";
export * from "./hooks/useDebounce";
export * from "./hooks/useLocalStorage";

0 comments on commit 182dbb8

Please sign in to comment.