refactor(ui,web): finding 1 — inline use-cookie into donation-toast, drop js-cookie from ui

The 107-line useCookie hook had exactly one consumer (donation-toast) that
only read + set-with-expiry. Inline the two Cookies.* calls directly and
delete the hook + its 128-line test. Drop js-cookie and @types/js-cookie
from packages/ui/package.json (apps/web retains its own js-cookie dep).

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
This commit is contained in:
Amruth Pillai
2026-07-04 21:57:10 +02:00
parent 79a69c5507
commit eab7534ea4
6 changed files with 21 additions and 265 deletions
-128
View File
@@ -1,128 +0,0 @@
import { act, renderHook } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_COOKIE_ATTRIBUTES, useCookie } from "./use-cookie";
type CookieAttributes = {
expires?: Date | number;
path?: string;
sameSite?: string;
secure?: boolean;
};
const cookieMock = vi.hoisted(() => {
const values = new Map<string, string>();
return {
get: vi.fn((name: string) => values.get(name)),
remove: vi.fn((name: string) => {
values.delete(name);
}),
reset: () => values.clear(),
set: vi.fn((name: string, value: string, _attributes?: CookieAttributes) => {
values.set(name, value);
}),
values,
};
});
vi.mock("js-cookie", () => ({
default: {
get: cookieMock.get,
remove: cookieMock.remove,
set: cookieMock.set,
},
}));
describe("useCookie", () => {
beforeEach(() => {
cookieMock.reset();
cookieMock.get.mockClear();
cookieMock.set.mockClear();
cookieMock.remove.mockClear();
});
it("reads an existing cookie value", () => {
cookieMock.values.set("theme", "dark");
const { result } = renderHook(() => useCookie("theme", "light"));
expect(result.current[0]).toBe("dark");
expect(cookieMock.set).not.toHaveBeenCalled();
});
it("sets the default value with secure defaults when the cookie is missing", () => {
const { result } = renderHook(() => useCookie("theme", "dark"));
expect(result.current[0]).toBe("dark");
expect(cookieMock.set).toHaveBeenCalledWith("theme", "dark", DEFAULT_COOKIE_ATTRIBUTES);
});
it("does not write a cookie when no default value is provided", () => {
const { result } = renderHook(() => useCookie("theme"));
expect(result.current[0]).toBeNull();
expect(cookieMock.set).not.toHaveBeenCalled();
});
it("updates the cookie value and state with secure defaults", () => {
const { result } = renderHook(() => useCookie("theme"));
act(() => {
result.current[1]("dark");
});
expect(result.current[0]).toBe("dark");
expect(cookieMock.set).toHaveBeenCalledWith("theme", "dark", DEFAULT_COOKIE_ATTRIBUTES);
});
it("passes through native expires cookie attributes", () => {
const { result } = renderHook(() => useCookie("session"));
const expires = new Date("2026-05-11T12:01:00.000Z");
act(() => {
result.current[1]("active", { expires });
});
const attributes = cookieMock.set.mock.calls.at(-1)?.[2] as CookieAttributes;
expect(attributes).toMatchObject({ path: "/", sameSite: "lax", secure: true });
expect(attributes.expires).toBe(expires);
});
it("does not expose a custom expiresMs option", () => {
const { result } = renderHook(() => useCookie("session"));
act(() => {
// @ts-expect-error use the native js-cookie expires attribute instead
result.current[1]("active", { expiresMs: 60_000 });
});
const attributes = cookieMock.set.mock.calls.at(-1)?.[2] as CookieAttributes & { expiresMs?: number };
expect(attributes).not.toHaveProperty("expiresMs");
});
it("allows explicit cookie attributes to override defaults", () => {
const { result } = renderHook(() => useCookie("theme"));
act(() => {
result.current[1]("dark", { sameSite: "strict", secure: false });
});
expect(cookieMock.set).toHaveBeenCalledWith("theme", "dark", {
...DEFAULT_COOKIE_ATTRIBUTES,
sameSite: "strict",
secure: false,
});
});
it("removes the cookie with secure defaults and clears state", () => {
cookieMock.values.set("theme", "dark");
const { result } = renderHook(() => useCookie("theme"));
act(() => {
result.current[2]();
});
expect(result.current[0]).toBeNull();
expect(cookieMock.remove).toHaveBeenCalledWith("theme", DEFAULT_COOKIE_ATTRIBUTES);
});
});
-107
View File
@@ -1,107 +0,0 @@
import Cookie from "js-cookie";
import { useCallback, useEffect, useMemo, useState } from "react";
type CookieAttributes = NonNullable<Parameters<typeof Cookie.set>[2]>;
type SharedCookieOptions = {
domain?: CookieAttributes["domain"];
partitioned?: boolean;
path?: CookieAttributes["path"];
sameSite?: CookieAttributes["sameSite"];
secure?: CookieAttributes["secure"];
};
type UseCookieOptions = SharedCookieOptions & {
expires?: CookieAttributes["expires"];
};
type UseCookieRemoveOptions = SharedCookieOptions;
export const DEFAULT_COOKIE_ATTRIBUTES = {
path: "/",
secure: true,
sameSite: "lax",
} as const satisfies UseCookieRemoveOptions;
const canUseCookies = () => typeof document !== "undefined";
const getCookie = (name: string) => {
if (!canUseCookies()) return null;
return Cookie.get(name) ?? null;
};
const compactCookieOptions = ({
domain,
expires,
partitioned,
path,
sameSite,
secure,
}: UseCookieOptions): CookieAttributes => ({
...(domain !== undefined && { domain }),
...(expires !== undefined && { expires }),
...(partitioned !== undefined && { partitioned }),
...(path !== undefined && { path }),
...(sameSite !== undefined && { sameSite }),
...(secure !== undefined && { secure }),
});
const resolveCookieOptions = (options?: UseCookieOptions): CookieAttributes => {
return {
...DEFAULT_COOKIE_ATTRIBUTES,
...(options && compactCookieOptions(options)),
};
};
const resolveRemoveOptions = (options?: UseCookieRemoveOptions): CookieAttributes => {
return {
...DEFAULT_COOKIE_ATTRIBUTES,
...(options && compactCookieOptions(options)),
};
};
export function useCookie(
name: string,
defaultValue?: string,
defaultOptions?: UseCookieOptions,
): readonly [
string | null,
(value: string, options?: UseCookieOptions) => void,
(options?: UseCookieRemoveOptions) => void,
] {
const initialValue = useMemo(() => getCookie(name) ?? defaultValue ?? null, [name, defaultValue]);
const [value, setValue] = useState<string | null>(initialValue);
useEffect(() => {
const cookie = getCookie(name);
let nextValue: string | null;
if (cookie !== null) {
nextValue = cookie;
} else if (defaultValue === undefined) {
nextValue = null;
} else {
if (canUseCookies()) Cookie.set(name, defaultValue, resolveCookieOptions(defaultOptions));
nextValue = defaultValue;
}
setValue(nextValue);
}, [name, defaultValue, defaultOptions]);
const updateCookie = useCallback(
(nextValue: string, options?: UseCookieOptions) => {
if (canUseCookies()) Cookie.set(name, nextValue, resolveCookieOptions(options));
setValue(nextValue);
},
[name],
);
const deleteCookie = useCallback(
(options?: UseCookieRemoveOptions) => {
if (canUseCookies()) Cookie.remove(name, resolveRemoveOptions(options));
setValue(null);
},
[name],
);
return [value, updateCookie, deleteCookie] as const;
}