From 639f5b371fff14018eb3badf5a13a4f4f1d278eb Mon Sep 17 00:00:00 2001 From: daishi Date: Fri, 16 Aug 2024 13:32:38 +0900 Subject: [PATCH] feat: add useAtomValue hook --- CHANGELOG.md | 4 +++ src/index.ts | 1 + src/useAtomValue.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/useAtomValue.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e71c02f..baa47df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- `useAtomValue` that doesn't resolve promises + ## [0.3.0] - 2024-05-24 ### Changed diff --git a/src/index.ts b/src/index.ts index 2ab1f2b..f3b4e16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export { atomWithPending } from './atomWithPending.js'; export { usePrepareAtoms } from './usePrepareAtoms.js'; +export { useAtomValue } from './useAtomValue.js'; diff --git a/src/useAtomValue.ts b/src/useAtomValue.ts new file mode 100644 index 0000000..d619d01 --- /dev/null +++ b/src/useAtomValue.ts @@ -0,0 +1,70 @@ +import { useDebugValue, useEffect, useReducer } from 'react'; +import type { ReducerWithoutAction } from 'react'; +import type { Atom, ExtractAtomValue } from 'jotai/vanilla'; +import { useStore } from 'jotai/react'; + +// Unlike useAtomValue from 'jotai/react', +// this hook doesn't resolve promises. + +type Store = ReturnType; + +type Options = Parameters[0] & { + delay?: number; +}; + +export function useAtomValue( + atom: Atom, + options?: Options, +): Value; + +export function useAtomValue>( + atom: AtomType, + options?: Options, +): ExtractAtomValue; + +export function useAtomValue(atom: Atom, options?: Options) { + const store = useStore(options); + + const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] = + useReducer< + ReducerWithoutAction, + undefined + >( + (prev) => { + const nextValue = store.get(atom); + if ( + Object.is(prev[0], nextValue) && + prev[1] === store && + prev[2] === atom + ) { + return prev; + } + return [nextValue, store, atom]; + }, + undefined, + () => [store.get(atom), store, atom], + ); + + let value = valueFromReducer; + if (storeFromReducer !== store || atomFromReducer !== atom) { + rerender(); + value = store.get(atom); + } + + const delay = options?.delay; + useEffect(() => { + const unsub = store.sub(atom, () => { + if (typeof delay === 'number') { + // delay rerendering to wait a promise possibly to resolve + setTimeout(rerender, delay); + return; + } + rerender(); + }); + rerender(); + return unsub; + }, [store, atom, delay]); + + useDebugValue(value); + return value; +}