---
title: "changelog"
description: "Changelog for @reactuses/core — release notes and version history for the ReactUse collection of 110+ React hooks."
canonical: https://reactuse.com/changelog/
---

# ChangeLog

## 6.5.9(Sep 17, 2026)

- fix(useMobileLandscape): use an ASCII pipe in the user-agent regex. The alternation between `Android` and `iphone` was written with a fullwidth vertical line (U+FF5C) rather than `|`, so the two alternatives were one literal string that no user agent contains. An Android user agent without a `Mobi` token — a tablet — was therefore never detected as mobile, and the hook reported landscape only for the `Mobi` case it happened to match. Thanks to @rawsun007 (#228)
- fix(useTextDirection): read the element's direction on the first render. `initialValue` was destructured with a default of `'ltr'`, so the `initialValue !== undefined` guard above it was always true and both the branch that reads the target element's `dir` and the server-side warning below it were unreachable. Every first render reported `ltr` and the effect corrected it afterwards, so a document with `dir="rtl"` rendered `ltr` once before settling, and server markup claimed `ltr` for a direction the hook had never read. The element now wins, falling back to `initialValue` and then `'ltr'` — what the unreachable branch already did. Two consequences worth knowing: `initialValue` is a fallback rather than an initial value now, and is documented as such; and the SSR warning is live again, so an app that server-renders without `initialValue` is told to set it instead of silently hydrating against a direction the server could not see. Adds `useTextDirection/index.spec.ts` — five tests, two of which fail on 6.5.8. Thanks to @rawsun007 (#230)

## 6.5.8(Sep 15, 2026)

- docs(readme): show the sponsors on the npm package page. The README that ships inside the package had fallen behind the repository one and carried neither the Special Sponsors wall nor the sponsor badge, so npmjs.com showed no sponsors at all. It now has the same Sponsors section, badge and "Support ReactUse" closing as the GitHub README. No code changes

## 6.5.7(Sep 15, 2026)

- fix(useObjectUrl): revoke the URLs it creates. The cleanup read `url` from the render closure, which belongs to the render before `setUrl` applied, so it was always `undefined` there and nothing was ever revoked: every object URL the hook created leaked for the lifetime of the page — on unmount and on every change of `object` — although the docs promised the opposite. The created URL is now held in a local `const` for the cleanup to release, and the hook returns `undefined` again once the object goes away instead of the URL of an object it no longer has. The argument is also typed as optional now (`object?: Blob | MediaSource`), so the documented `useObjectUrl(file)` with `file` from `useState<File>()` typechecks. Adds `useObjectUrl/index.spec.ts` — five tests, three of which fail on 6.5.6. Thanks to @rawsun007 (#226)
- fix(useKeyModifier): detach the listeners and follow a changed modifier. The four `document` listeners were registered through `useMount`, which discards the callback's return value, so their cleanup never ran — and even called directly it passed freshly built arrow functions to `removeEventListener`, which matches by reference. Every mount left four more listeners on `document`, each calling `setState` on an unmounted component whenever a key or button was pressed. `useMount`'s empty dependency list also froze the arguments, so going from `useKeyModifier('Shift')` to `useKeyModifier('Control')` kept reporting Shift, and the same held for `events`. Now registered in a `useDeepCompareEffect` keyed on `[modifier, events]` with one hoisted handler, the shape `useEventListener` already uses. Adds `useKeyModifier/index.spec.ts` — seven tests, three of which fail on 6.5.6. Thanks to @rawsun007 (#223)
- fix(useDoubleClick): keep the click count across renders and cancel the timer. `handle(...)` ran during render, so the `let count = 0` inside it was rebuilt on every render while `useEventListener` dispatched to the newest closure: a re-render between two clicks reset the count and both reported as single clicks. The `setTimeout` id was never stored either, so nothing could cancel it and the callback ran after unmount. Count and timer now live in a ref, one pair per event type so `click` and `touchend` stay independent; only the first click arms a timer, the teardown clears it, the count is reset before the callbacks run so a throwing callback cannot wedge the hook, and both callbacks go through `useLatest`. Spec grown from a placeholder to twelve tests, five of which fail on 6.5.6. Thanks to @rawsun007 (#224)
- fix(useMousePressed): attach the `touch` and `drag` listeners to a ref target. The target was resolved during render, where a ref handed to a child is still `null`, and the effect depended on that resolved element so it never re-ran to pick it up — with the usual `useRef(null)` pattern those listeners were never attached at all, while `mousedown` worked because it goes through `useEventListener`. The target now resolves inside the effect, keyed on `useStableTarget`. `onPressed` was also curried, so the add and the remove received different functions and `dragstart` and `touchstart` were never detached; there is one stable handler each now. Adds `useMousePressed/index.spec.ts` — six tests, two of which fail on 6.5.6. Thanks to @rawsun007 (#225)

## 6.5.6(Sep 7, 2026)

- fix(useIdle): cancel the timers on unmount. The effect's cleanup removed the event listeners but left both timers running: the idle countdown, which is `ms` long and a minute by default, and the 50ms timer inside the throttled activity handler. The `mounted` flag stopped the state update, so nothing warned — the timers just kept the effect's closure alive until they fired, and `jest.getTimerCount()` still read 1 after unmount. Both are now cancelled in cleanup, matching how `useThrottleFn` and `useDebounceFn` already cancel theirs. The hook gains its first spec file — five tests, two of which fail on 6.5.5. Thanks to @rawsun007 (#222)
- fix(useCycleList): derive the next index from the current state. `set` read `index` from the render closure, so two calls in the same batch both computed from the same value and the second overwrote the first — calling `next()` twice in one handler advanced a single step, and `next()` followed by `prev()` did not cancel out. An empty list also made `(index + i) % 0` NaN, which was stored as the index and never recovered. Both now go through the functional updater form, matching `useCounter`, and an empty list leaves the index untouched. Adds `useCycleList/index.spec.ts` — eight tests, three of which fail on 6.5.5. Thanks to @rawsun007 (#221)

## 6.5.5(Aug 20, 2026)

- fix(useInfiniteScroll): stop firing `onLoadMore` on every render. The load-more effect listed `target` in its dependencies, and a getter target such as `() => element` — one of the documented `BasicTarget` forms — is a new function on every render, so the effect re-ran every render and called `onLoadMore` each time. Loading more data appends to state and renders again, so it never settled: three renders produced three loads, against zero for a ref target (the form the docs demo). The target now resolves through `useStableTarget`, so the effect is keyed on the element rather than on the identity of the getter. The hook gains its first spec file — nine tests covering both target forms
- fix(useCssVar): stop rebuilding the MutationObserver and the returned setter on every render. `set` and `updateCssVar` were memoised on `[target, prop]`, and the effect that installs the observer depended on both plus `target`, so a getter target invalidated all of them every render. With `observe: true` the observer was disconnected and reconstructed each render — four observers across three renders, against one for a stable element — which both wastes work and leaves a gap where mutations go unseen; and the `set` handed back to callers changed identity every render, invalidating any consumer memo built on it. The target now resolves through `useStableTarget` and the callbacks read it from the ref, so `set` is stable for the hook's lifetime and the observer is built once per element. Spec grown from a stub to eleven tests
- fix(useScrollLock): key the lock effect on the element rather than on the getter. Follow-up to the 6.5.4 snapshot fix, which made the effect idempotent but left it re-running on every render whenever `target` was the `() => document.body` getter the docs recommend — re-writing `overflow: hidden` each time, and a trap for anything added to that effect later. `useStableTarget` ties it to the element it actually locks; the element-keyed snapshot stays as the invariant that guarantees one read per lock
- chore: audited every hook that accepts a `BasicTarget`. `useEventListener`, `useIntersectionObserver`, `useMutationObserver`, `useResizeObserver` and `useSticky` already resolved theirs through `useStableTarget`; the three above were the only ones depending on a raw target. The rest reach the DOM through `useEventListener`, `useResizeObserver` or `useEvent`, all of which are already stable

## 6.5.4(Aug 20, 2026)

- fix(useEventListener): pass the listener options back to `removeEventListener`. Registration called `addEventListener(name, fn, options)` but cleanup called `removeEventListener(name, fn)` with the options dropped, and the DOM matches a listener for removal on (type, callback, capture) — so anything registered with `{ capture: true }` (or the boolean `true` form) was never detached. A capture-phase listener kept firing after the component unmounted, survived a change of `eventName` or `options`, and accumulated one live listener per mount: a modal that intercepts clicks on `document` in the capture phase went on intercepting them for the rest of the session. `passive`, `once` and `{ capture: false }` were unaffected, since only `capture` participates in the removal match. Verified in jsdom and Chrome 151
- fix(useScrollLock): snapshot the original `overflow` once per lock, not on every render. The effect that applies the lock lists `target` in its dependencies, and the `() => document.body` getter the docs recommend for SSR safety is a fresh function on every render, so the effect re-ran constantly and re-executed `initialOverflowRef.current = element.style.overflow`. While locked that reads back the `hidden` the hook itself just wrote, so a single unrelated re-render with the modal open poisoned the saved value: `unlock()` then restored `overflow: hidden` and the page never scrolled again, an existing inline value such as `overlay` was swallowed, and the v6.5.3 unmount cleanup restored the poisoned value too. Only the happy path — lock, then unlock with no render in between — behaved correctly, which is why hand testing never caught it. The snapshot is now keyed on the locked element, so it happens once per lock however often the effect re-runs; if the target moves while locked, the element being released is restored before the new one is captured

## 6.5.3(Aug 19, 2026)

- fix(useScrollLock): release the lock when the owning component unmounts. The lock is an inline `overflow: hidden` written onto an element the hook does not own, plus an iOS `touchmove` guard registered with `passive: false`, and neither was ever cleaned up — unmounting while locked (a route change with the modal still open is the common one) left the page permanently unscrollable, and on iOS left a listener attached to `document.body` cancelling every touch scroll for the rest of the session. The hook now restores the exact inline `overflow` it replaced and detaches the guard on unmount, which is the behaviour the docs already promised. Unmounting while unlocked still touches nothing

## 6.5.2(Aug 17, 2026)

- fix(useClipboard): work in non-secure contexts, and expose Clipboard API support (#218, #219). `navigator.clipboard` is `undefined` whenever `window.isSecureContext` is false — plain `http://`, LAN IPs, some embedded webviews — so `copy()` rejected and the hook was unusable there. Copying now falls back to a temporary `<textarea>` plus `document.execCommand('copy')`, and `copy`/`cut` events fall back to the current document selection when a Clipboard API read is unavailable or denied. The returned tuple gains a third element, `isSupported`, reporting whether `navigator.clipboard` exists — the copy fallback works either way, so treat it as a capability hint rather than a gate. The selection is captured synchronously inside the event, because by the time an awaited continuation runs the browser has already cleared it on `cut` (Firefox) or torn down the fallback textarea. Verified against Chromium, Firefox and WebKit. Thanks @tanukihee

## 6.5.1(Aug 15, 2026)

- fix(useInterval): don't run the `immediate` callback while paused (#128). `immediate: true` invoked the callback inside the effect unconditionally, so a `delay` flipping to `null` — the documented "stop the timer" value — still ran it once at the moment of pausing (e.g. a poll firing exactly when `visible && online ? 10_000 : null` went offline). The immediate call is now guarded on `delay !== null`; `controls: true` behaviour is unchanged. Thanks @vincerubinetti
- fix(useEventSource): honor `autoReconnect.retries` and cancel a pending reconnect on unmount. `open()` reset the retry counter on every call, but the reconnect path was `setTimeout(open, delay)`, so each retry wiped the counter and `retries` could never be exceeded (a flapping server retried forever and `onFailed` never fired). Reconnects now go through an internal `connect()` that keeps the count; a successful `onopen` resets it; the public `open()` starts a fresh budget. The reconnect timer is tracked and cleared from `close()`, so unmounting (or an explicit `close()`) during the delay no longer spawns a fresh `EventSource` for a component that's gone
- docs(useInterval): describe what `immediate` and `controls` actually do — `immediate` runs the callback once whenever the interval starts (mount and every `delay` change, never while `null`); `controls` switches to manual `resume()` / `pause()` instead of auto-starting from `delay` (#128)

## 6.5.0(Aug 5, 2026)

- build(core): ship per-module `dist` (bunchee → tsdown, `unbundle`) so barrel-file optimizers such as Next.js `optimizePackageImports` can unroll `@reactuses/core` imports (#216). A Next.js dev page importing only `useDebounce` previously pulled every hook into one 552 kB chunk; it now loads just `useDebounce` and its dependency chain (64 kB, −88%). Every hook is also importable as a subpath (`@reactuses/core/useDebounce`) via a new `./*` export; the CJS entry moves to `./dist/index.js` / `./dist/index.d.ts`

## 6.4.2(Jul 31, 2026)

- fix(useOrientation): correct the inverted `isBrowser` guards in `lockOrientation`/`unlockOrientation` (#215). Both early-returned *in* the browser and only ran during SSR, making them no-ops everywhere it mattered; the guards now return when there is no browser. Thanks @ostapondo
- fix(useInterval): clear a manually resumed interval on unmount (#212). With `controls: true` the effect registered no cleanup, so an interval started through `resume()` kept firing after the component unmounted; the cleanup now runs in its own mount-scoped effect. `resume()` also clears a running timer before starting a new one, so calling it twice no longer leaks an interval that `pause()` can't reach. Thanks @ostapondo
- fix(useMicrophone): reset `level` to 0 on `stop()` (#213). The rAF loop is the only writer of `level`, so a meter bound to it stayed frozen at the last reading long after the microphone was released. Thanks @ostapondo
- fix(useElementByPoint): avoid a re-render on every frame in `multiple` mode (#214). `elementsFromPoint` allocates a fresh array on every call, so the hit list is now compared element-by-element and the previous state is kept when nothing changed. Thanks @ostapondo

## 6.4.1(Jul 28, 2026)

- fix(useScriptTag): handle the rejection from the `immediate` auto-load (#206). The auto-load called `load()` as a bare statement, so when a script failed to load — blocked by an ad blocker, offline, 404 — the rejected promise reached `window.onunhandledrejection` and got reported by error trackers. It now attaches a no-op catch; `status === 'error'` remains the reporting channel, and an explicit `load()` still rejects for callers that hold the promise themselves. Thanks @Faithfinder

## 6.3.0(Mar 24, 2026)

- refactor(createStorage): replace `useState` + `useDeepCompareEffect` with `useSyncExternalStore`, eliminating CSR first-render flicker, SSR hydration mismatches, and stale cross-tab reads (#195)
- fix(createStorage): fix stale closure in consecutive functional updates within the same synchronous batch — `updateState` now reads from `getSnapshot()` instead of the render-time `state`
- test(useLocalStorage): add 12 new test cases covering cross-tab sync, `storage.clear()`, `listenToStorageChanges` toggle, three-state semantics, `onError`, `mountStorageValue`, and consecutive functional updates

## 6.1.12(Mar 10, 2026)

- fix(useGeolocation): make useSupported check more robust

## 6.1.11(Jan 20, 2026)

- fix(usePageLeave): fix infinite re-render issue caused by unstable handler references  
- fix(useEventListener): improve stability to prevent unnecessary event listener re-bindings while maintaining support for ref-based targets. Uses stable element identifiers: for refs, tracks the ref object itself; for functions/direct elements, tracks the resolved actual element. This allows function-based targets like `() => document` to work without causing infinite loops, while still re-binding when the actual target element changes
- fix(useSticky): fix infinite re-render issue when passing unstable function references as target or scroll element
- fix(useMutationObserver): add missing `target` dependency - now correctly re-observes when target element changes
- fix(useResizeObserver): fix infinite re-render issue when passing unstable function references as target
- fix(useIntersectionObserver): add missing `target` dependency - now correctly re-observes when target element changes
- feat(useStableTarget): add new internal utility hook for creating stable identifiers for BasicTarget parameters that can be safely used in effect dependencies. This solves the common problem where passing unstable function references like `() => document` would cause infinite re-renders

## 6.1.9(Jan 2026)

- fix(useRafState): fix bug where multiple consecutive functional updates would only apply the last one. Now correctly accumulates all updates within the same animation frame, matching React's useState behavior. For example, calling `setState(n => n + 1)` three times consecutively will now correctly increase the value by 3 instead of 1.

## 6.1.8(Dec 2025)

- fix(useMap): fix type parameter support by moving generics into function signature, now `useMap<string, number>()` works correctly

## 6.1.6(Nov 21, 2025)

- fix(createStorage): use `useLatest` to avoid unnecessary re-renders and simplify dependency arrays

## 6.1.2(Oct 30, 2025)

- feat: add useScratch hook.

## 6.1.1(Oct 22, 2025)

- feat: add useSpeechRecognition hook.

## 6.1.0(Sep 24, 2025)

**Breaking Changes**:

- **useDarkMode**: Storage format changed from boolean to string values ('dark'/'light'). This affects localStorage/sessionStorage data and SSR scripts. Existing stored boolean values will be automatically migrated, but custom SSR scripts need to be updated to handle string comparisons instead of boolean values.

**Features**:

- feat: add useMap hook for reactive Map state management with set, get, remove, has, clear, and reset operations
- feat: add useColorMode hook with support for multiple color modes beyond dark/light
- feat: add useBoolean hook for boolean state management with setValue, setTrue, setFalse, and toggle operations
- feat: add useClickAway alias for useClickOutside hook
- feat: add useCopyToClipboard alias for useClipboard hook
- feat: add comprehensive documentation for useColorMode hook
- docs: add context provider examples for both useColorMode and useDarkMode  
- docs: add multi-color theme examples in useColorMode documentation (6 themes: light, dark, blue, green, purple, sepia)
- docs: update useDarkMode documentation scripts to handle new string storage format

## 6.0.5(Jul 03, 2025)

- fix(useClipboard): add document focus check

## 6.0.4(Jul 02, 2025)

- fix(useClipboard): immediately access clipboard

## 6.0.3(May 23, 2025)

- fix(useEventSource): improve EventSource connection handling

## 6.0.2(May 06, 2025)

- fix(react native): Property 'document' doesn't exist.

## 6.0.0(Dec 20, 2024)

**Breaking Changes**: Modified the runtime behavior of the following hooks to ensure concurrent mode safety:

- useActiveElement
- useCustomCompareEffect
- useDarkMode
- useLatest
- usePrevious
- useMouse

Warning: These changes will affect your applications. Using unstable references as prop parameters in these hooks may trigger infinite React re-renders, since these props are now included in the hooks' dependency arrays.

All DOM parameter passing now requires a stable reference. When using SSR mode, you might commonly pass functions like () => window. Make sure to extract these functions to the outer scope to maintain a stable reference, for example:

If you're passing DOM parameters using refs, you don't need to worry about this issue since refs always maintain stable references.

```js
// Don't do this:
function Component() {
  useHook(() => window)
}

// Do this instead:
const getWindow = () => window
function Component() {
  useHook(getWindow)
}
```

**Chore**: List React19 as Dependency.

## 5.0.23(Nov 11, 2024)

- feat: add useFetchEventSource

## 5.0.21(Aug 16, 2024)

- feat(useLocalStorage): deprecated `effectStorageValue`, rename to `mountStorageValue`. add `listenToStorageChanges` option, default to `true`

## 5.0.20(Aug 12, 2024)

- fix(useActiveElement): add init state

## 5.0.19(July 25, 2024)

- feat: add usePreferredLanguages
- feat: add useBroadcastChannel
- feat: add useDevicePixelRatio
- feat: add useElementByPoint
- feat: add useQRCode
- fix next.js import error: `SyntaxError: Named export 'a' not found. The requested module './index-client-Qon46B4S.js' is a CommonJS module, which may not support all module.exports as named exports.`. which caused by `use client` with bunchee;

## 5.0.18(July 17, 2024)

- fix: add useGeolocation navigator support check

## 5.0.17(July 16, 2024)

- feat: add use polyfill

## 5.0.16(July 15, 2024)

- feat: add useMergedRefs

## 5.0.15(June 05, 2024)

- feat: add useDisclosure
- feat: add useEventSource
- feat: add useControlled

## 5.0.14(May 17, 2024)

- fix(useLocalStorage): remove extra render caused by diff default value

## 5.0.13(April 29, 2024)

- useFileDialog: add files return in `open`
- docs: fix search

## 5.0.12(April 28, 2024)

- support `Pauseble` with useInterval

## 5.0.11(April 12, 2024)

- Fixed the issue where useOnceEffect does not execute the cleanup function

## 5.0.10(March 17, 2024)

- add deps in usePlatform useCallback

## 5.0.9(March 17, 2024)

- add usePlatform
- add useMobileLandscape

## 5.0.8(March 12, 2024)

- fix(useWindowScroll): ssr error

## 5.0.7(March 01, 2024)

- restore target element
- feat(useClickOutSide): support enable prop

## 5.0.6(Feb 28, 2024)

- feat(useDraggable): support mannual set position

## 5.0.5(Feb 22, 2024)

- feat(useDraggable): improve boundary for scroll container

## 5.0.4(Feb 22, 2024)

- fix(useLocalStorage): use `useDeepCompareEffect`

## 5.0.3(Feb 22, 2024)

- feat(useDraggable): add container boundary

## 5.0.2(Feb 22, 2024)

- fix(useDraggable): remove max call effect.

## 5.0.1(Feb 22, 2024)

- fix(useLocalStorage): remove `defaultValue` in deps

## 5.0.0(Feb 05, 2024)

**Website**:

- New website
- Use comments when generating the types of APIs.

**Breaking Change**:

- Element target support dropped
- For a complex object passed in, adopt deep comparison when used in an effect.
- Delete `useControlled` and `useVirtualList`

## 4.0.11(Jan 09, 2024)

- perf: reduce useWindowSize re-render

## 4.0.10(Oct 08, 2023)

- feat: add useLocationSelector

## 4.0.9(Oct 07, 2023)

- feat: add useWebNotification

## 4.0.8(Sep 21, 2023)

- usePermission: Do not fail on Safari 15

- move types package to devDeps

## 4.0.7(Sep 3, 2023)

- feat: add useCssVar, useScreenSafeArea

## 4.0.6(Aug 27, 2023)

- feat: add useHover hook

## 4.0.3(Ju1 26, 2023)

- fix useElementVisibility
- fix external deps

## 4.0.1(Ju1 26, 2023)

- fix useClickOutside.

## 4.0.0(Ju1 22, 2023)

- options are now consider reference immutable.

## 3.0.3(Jul 18, 2023)

- fix useMediaDevices when create default value;

## 3.0.2(Jul 18, 2023)

- fix useMediaDevices in firefox.

## 3.0.1(Jul 18, 2023)

- useMediaDevices support request permissons, and change return result to array.

## 3.0.0(Jul 7, 2023)

- Dynamic dom element passing parameters is now supported

## 2.2.9(Jul 6, 2023)

- add useSetState
- add useMeasure

## 2.2.6(May 8, 2023)

- fix esm import error

## 2.2.5(April 6, 2023)

- add useSupported
- add useTextSelection
- add useEyeDropper

## 2.2.4(March 30, 2023)

- add useCountDown Hook

## 2.2.3(March 14, 2023)

- fix bug in useDarkMode
- delete option ignoreDefault in useLocalStorage and useSessionStorage
- add option csrData in useLocalStorage and useSessionStorage

## 2.2.2(March 10, 2023)

- add test for useActiveElement
- fix bug in useDarkMode
- add Option ignoreDefault in useLocalStorage and useSessionStorage

## 2.2.0(March 8, 2023)

- fixed some ssr error
- fixed some test

## 2.0.1(March 4, 2023)

- fixed some ssr error
- fixed some test

## 2.0.0(March 4, 2023)

- fixed some ssr error
- add useScrollIntoView
- add useSticky
- add useReducedMotion

## 1.1.3(Jan 21, 2023)

- add useControlled

## 1.1.2(Jan 20, 2023)

- add useFocus

## 1.0.7(September 28, 2022)

- add useClickOutside
- add useClipboard
- add useCycleList
- refactor(useMediaQuery): rewrite with uses

## 1.0.6(September 24 ,2022)

- fix useWindowScroll error

## 1.0.5 (September 24 ,2022)

- add useWindowScroll
- add useWindowSize

## 1.0.4 (September 22 ,2022)

- add useElementVisibility hook
- add useWindowsFocus hook

## 1.0.3 (September 20 ,2022)

- add useElemenBounding hook

## 1.0.2 (September 20 ,2022)

- add useActiveElement
- add useDraggable

## 1.0.0 (September 18 ,2022)

- Initial public release