JotaiJotai

状態
Primitive and flexible state management for React

atomWithDebounce

atomWithDebounce helps with creating an atom where state set should be debounced.

This util is useful for text search inputs, where you would like to call functions in derived atoms only once after waiting for a duration, instead of firing an action on every keystroke.

import { atom, SetStateAction } from 'jotai'
export default function atomWithDebounce<T>(
initialValue: T,
delayMilliseconds = 500,
shouldDebounceOnReset = false,
) {
const prevTimeoutAtom = atom<ReturnType<typeof setTimeout> | undefined>(
undefined,
)
// DO NOT EXPORT currentValueAtom as using this atom to set state can cause
// inconsistent state between currentValueAtom and debouncedValueAtom
const _currentValueAtom = atom(initialValue)
const isDebouncingAtom = atom(false)
const debouncedValueAtom = atom(
initialValue,
(get, set, update: SetStateAction<T>) => {
clearTimeout(get(prevTimeoutAtom))
const prevValue = get(_currentValueAtom)
const nextValue =
typeof update === 'function'
? (update as (prev: T) => T)(prevValue)
: update
const onDebounceStart = () => {
set(_currentValueAtom, nextValue)
set(isDebouncingAtom, true)
}
const onDebounceEnd = () => {
set(debouncedValueAtom, nextValue)
set(isDebouncingAtom, false)
}
onDebounceStart()
if (!shouldDebounceOnReset && nextValue === initialValue) {
onDebounceEnd()
return
}
const nextTimeoutId = setTimeout(() => {
onDebounceEnd()
}, delayMilliseconds)
// set previous timeout atom in case it needs to get cleared
set(prevTimeoutAtom, nextTimeoutId)
},
)
// exported atom setter to clear timeout if needed
const clearTimeoutAtom = atom(null, (get, set, _arg) => {
clearTimeout(get(prevTimeoutAtom))
set(isDebouncingAtom, false)
})
return {
currentValueAtom: atom((get) => get(_currentValueAtom)),
isDebouncingAtom,
clearTimeoutAtom,
debouncedValueAtom,
}
}

Caveat

Please note that this atom has different objectives from concurrent features in React 18 such as useTransition and useDeferredValue whose main aim is to prevent blocking of interaction with the page for expensive updates.

For more info, please read this github discussion https://github.com/reactwg/react-18/discussions/41 under the section titled "How is it different from setTimeout?"

Example Usage

The sandbox link below shows how we would use a derived atom to fetch state based on the value of debouncedValueAtom.

When typing a pokemon's name in <SearchInput>, we do not send a get request on every letter, but only after delayMilliseconds has passed since the last text input.

This reduces the number of backend requests to the server.