import { useCallback, useContext, useDebugValue, useEffect, useReducer,} from 'react'import type { Reducer } from 'react'import type { Atom, Scope } from './atom'import { getScopeContext } from './contexts'import { COMMIT_ATOM, READ_ATOM, SUBSCRIBE_ATOM } from './store'import type { VersionObject } from './store'
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T
export function useAtomValue<Value>( atom: Atom<Value>, scope?: Scope): Awaited<Value> { const ScopeContext = getScopeContext(scope) const { s: store } = useContext(ScopeContext)
const getAtomValue = useCallback( (version?: VersionObject) => { const atomState = store[READ_ATOM](atom, version) if ('e' in atomState) { throw atomState.e } if ('p' in atomState) { throw atomState.p } if ('v' in atomState) { return atomState.v as Awaited<Value> } throw new Error('no atom value') }, [store, atom] )
const [[version, value, atomFromUseReducer], rerenderIfChanged] = useReducer< Reducer< readonly [VersionObject | undefined, Awaited<Value>, Atom<Value>], VersionObject | undefined >, undefined >( useCallback( (prev, nextVersion) => { const nextValue = getAtomValue(nextVersion) if (Object.is(prev[1], nextValue) && prev[2] === atom) { return prev } return [nextVersion, nextValue, atom] }, [getAtomValue, atom] ), undefined, () => { const initialVersion = undefined const initialValue = getAtomValue(initialVersion) return [initialVersion, initialValue, atom] } )
if (atomFromUseReducer !== atom) { rerenderIfChanged(undefined) }
useEffect(() => { const unsubscribe = store[SUBSCRIBE_ATOM](atom, rerenderIfChanged) rerenderIfChanged(undefined) return unsubscribe }, [store, atom])
useEffect(() => { store[COMMIT_ATOM](atom, version) })
useDebugValue(value) return value}