diff --git a/.changeset/hot-bats-report.md b/.changeset/hot-bats-report.md new file mode 100644 index 00000000..d379cb70 --- /dev/null +++ b/.changeset/hot-bats-report.md @@ -0,0 +1,7 @@ +--- +'usehooks-ts': major +--- + +Add `priority` setting to `useLocalStorage`. + +For persisted settings that affect initial render and the page layout, you can use `{ priority: 'max' }`; this will cause the hook to compose `useLayoutEffect` instead of `useEffect` to ensure the most rapid update and help avoid UI jumpiness. diff --git a/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.md b/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.md index 3eac0881..7b918a6c 100644 --- a/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.md +++ b/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.md @@ -1,7 +1,9 @@ Persist the state with [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) so that it remains after a page refresh. This can be useful for a dark theme. This hook is used in the same way as useState except that you must pass the storage key in the 1st parameter. -You can also pass an optional third parameter to use a custom serializer/deserializer. +You can also pass an optional third parameter to use a custom serializer/deserializer or to set the priority of sync activities. + +For example, with `{ priority: 'max' }`, the hook will use `useLayoutEffect` to ensure the most rapid update; ideal for persisted UI settings that affect initial render and the page layout. **Note**: If you use this hook in an SSR context, set the `initializeWithValue` option to `false`, it will initialize in SSR with the initial value. diff --git a/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.ts b/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.ts index 01a09a3f..3ff86e67 100644 --- a/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.ts +++ b/packages/usehooks-ts/src/useLocalStorage/useLocalStorage.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useEffect, useLayoutEffect, useState } from 'react' import type { Dispatch, SetStateAction } from 'react' @@ -26,6 +26,13 @@ type UseLocalStorageOptions = { * @default true */ initializeWithValue?: boolean + /** + * If 'max', useLayoutEffect will be used to ensure the most rapid update; ideal for + * UI settings that affect initial render and the page layout. + * If 'default', useEffect will be used to gracefully update without blocking the UI. + * @default 'default' + */ + priority?: 'max' | 'default' } const IS_SERVER = typeof window === 'undefined' @@ -165,10 +172,13 @@ export function useLocalStorage( window.dispatchEvent(new StorageEvent('local-storage', { key })) }) - useEffect(() => { - setStoredValue(readValue()) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [key]) + const effectCallback = useCallback(() => setStoredValue(readValue()), []) + + if (!IS_SERVER && options.priority === 'max' ) { + useLayoutEffect(effectCallback, [key]) + } else { + useEffect(effectCallback, [key]) + } const handleStorageChange = useCallback( (event: StorageEvent | CustomEvent) => {