Feat/compt 30 state storage hooks#3
Conversation
There was a problem hiding this comment.
Pull request overview
Adds the first batch of “state & storage” hooks to the HooksKit library, including shared storage helpers and accompanying tests/docs, aligning with the project goal of SSR-safe, zero-runtime-deps hooks.
Changes:
- Introduces
useDebounce,useLocalStorage, anduseSessionStoragehooks undersrc/hooks/. - Adds
storage.tshelper for SSR-guarded JSON read/write and Vitest hook tests. - Updates hooks barrel exports, repo instructions, changeset metadata, and Husky pre-commit hook.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/hooks/useSessionStorage.ts | New sessionStorage-backed state hook using shared storage helpers |
| src/hooks/useSessionStorage.test.ts | Tests for session storage hook behavior and JSON fallback |
| src/hooks/useLocalStorage.ts | New localStorage-backed state hook using shared storage helpers |
| src/hooks/useLocalStorage.test.ts | Tests for local storage hook behavior and JSON fallback |
| src/hooks/useDebounce.ts | New debounced-value hook |
| src/hooks/useDebounce.test.ts | Tests for debounce timing behavior |
| src/hooks/storage.ts | Shared SSR-guarded JSON read/write helpers for storage hooks |
| src/hooks/index.ts | Exports newly added hooks from the hooks barrel |
| .husky/pre-commit | Simplifies pre-commit hook to run lint-staged |
| .github/instructions/copilot-instructions.md | Updates repo/module guidance to HooksKit + SSR-safe hook requirements |
| .changeset/COMPT-30-state-storage-hooks.md | Declares a minor release for the new hooks |
| export function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] { | ||
| const storage = typeof window === 'undefined' ? undefined : window.localStorage; | ||
|
|
||
| const [storedValue, setStoredValue] = useState<T>(() => { | ||
| return readStorageValue(storage, key, initialValue); | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| writeStorageValue(storage, key, storedValue); | ||
| }, [key, storedValue, storage]); | ||
|
|
||
| return [storedValue, setStoredValue]; | ||
| } |
There was a problem hiding this comment.
The state initializer reads from storage only on the first render. If key changes between renders, the hook will not re-read the new key; instead the effect will write the previous storedValue into the new key. Either document/assume key is stable (and remove key from the write effect deps), or add logic to re-sync storedValue from storage when key changes.
| return readStorageValue(storage, key, initialValue); | ||
| }); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
The state initializer reads from storage only on the first render. If key changes between renders, the hook will not re-read the new key; instead the effect will write the previous storedValue into the new key. Either document/assume key is stable (and remove key from the write effect deps), or add logic to re-sync storedValue from storage when key changes.
| useEffect(() => { | |
| useEffect(() => { | |
| if (!storage) { | |
| return; | |
| } | |
| setStoredValue(readStorageValue(storage, key, initialValue)); | |
| }, [initialValue, key, storage]); | |
| useEffect(() => { |
| const value = readStorageValue(undefined, 'ssr', 'fallback'); | ||
| expect(value).toBe('fallback'); |
There was a problem hiding this comment.
This test name suggests it's validating the useSessionStorage hook, but the assertion is actually against readStorageValue. Either rename the test to reflect what it's covering, or render the hook with window unavailable to validate the hook-level SSR behavior.
| const value = readStorageValue(undefined, 'ssr', 'fallback'); | |
| expect(value).toBe('fallback'); | |
| const { result } = renderHook(() => useSessionStorage('ssr', 'fallback')); | |
| expect(result.current[0]).toBe('fallback'); |
| it('returns initial value when window is undefined (SSR guard)', () => { | ||
| vi.stubGlobal('window', undefined); | ||
|
|
||
| const value = readStorageValue(undefined, 'ssr', 'fallback'); | ||
| expect(value).toBe('fallback'); | ||
| }); |
There was a problem hiding this comment.
This test name suggests it's validating the useLocalStorage hook, but the assertion is actually against readStorageValue. Either rename the test to reflect what it's covering, or render the hook with window unavailable to validate the hook-level SSR behavior.
| const timeoutId = window.setTimeout(() => { | ||
| setDebouncedValue(value); | ||
| }, delay); | ||
|
|
||
| return () => { | ||
| window.clearTimeout(timeoutId); |
There was a problem hiding this comment.
useDebounce directly references window.setTimeout/clearTimeout. The repo guidelines state hooks must guard all window/document access (typeof window === 'undefined'). Consider using the global setTimeout/clearTimeout (or globalThis) and/or short-circuiting when window is unavailable to keep the hook usable outside the browser (SSR/tests/non-DOM runtimes).
| const timeoutId = window.setTimeout(() => { | |
| setDebouncedValue(value); | |
| }, delay); | |
| return () => { | |
| window.clearTimeout(timeoutId); | |
| const timeoutId = setTimeout(() => { | |
| setDebouncedValue(value); | |
| }, delay); | |
| return () => { | |
| clearTimeout(timeoutId); |
…ooks - useDebounce<T>(value, delay): returns debounced value, resets timer on value/delay change - useLocalStorage<T>(key, initial): syncs with localStorage, SSR-safe, JSON serialization - useSessionStorage<T>(key, initial): same pattern for sessionStorage - Shared storage.ts helper with readStorageValue/writeStorageValue (SSR guard + parse fallback) - All three exported from src/hooks/index.ts -> src/index.ts - Full test coverage: timer reset, JSON sync, parse error fallback, SSR guard - tsc --noEmit passes, lint passes (0 warnings), 13/13 tests pass
…y pre-commit - .changeset/COMPT-30-state-storage-hooks.md: minor bump summary for 0.0.1 release - .github/instructions/copilot-instructions.md: updated to HooksKit package identity, real src structure with COMPT-30 hooks marked, COMPT-XX branch naming convention - .husky/pre-commit: removed deprecated husky v9 shebang lines (breaks in v10)
b6ce22a to
7004ad3
Compare
|



Summary
Why
Checklist
npm run lintpassesnpm run typecheckpassesnpm testpassesnpm run buildpassesnpx changeset) if this affects consumersNotes