diff --git a/src/react/useAtom.ts b/src/react/useAtom.ts index a504a079c8..29784db288 100644 --- a/src/react/useAtom.ts +++ b/src/react/useAtom.ts @@ -29,7 +29,9 @@ export function useAtom( options?: Options, ): [Awaited, never] -export function useAtom>( +export function useAtom< + AtomType extends WritableAtom, +>( atom: AtomType, options?: Options, ): [ @@ -37,7 +39,7 @@ export function useAtom>( SetAtom, ExtractAtomResult>, ] -export function useAtom>( +export function useAtom>( atom: AtomType, options?: Options, ): [Awaited>, never] diff --git a/src/react/useAtomValue.ts b/src/react/useAtomValue.ts index 0e3baa1297..8c28b62ebd 100644 --- a/src/react/useAtomValue.ts +++ b/src/react/useAtomValue.ts @@ -50,7 +50,7 @@ export function useAtomValue( options?: Options, ): Awaited -export function useAtomValue>( +export function useAtomValue>( atom: AtomType, options?: Options, ): Awaited> diff --git a/src/react/useSetAtom.ts b/src/react/useSetAtom.ts index 47a20747cd..167fa068a4 100644 --- a/src/react/useSetAtom.ts +++ b/src/react/useSetAtom.ts @@ -14,7 +14,9 @@ export function useSetAtom( options?: Options, ): SetAtom -export function useSetAtom>( +export function useSetAtom< + AtomType extends WritableAtom, +>( atom: AtomType, options?: Options, ): SetAtom, ExtractAtomResult> diff --git a/src/react/utils/useHydrateAtoms.ts b/src/react/utils/useHydrateAtoms.ts index a703caa759..dad27cfc28 100644 --- a/src/react/utils/useHydrateAtoms.ts +++ b/src/react/utils/useHydrateAtoms.ts @@ -5,11 +5,11 @@ type Store = ReturnType type Options = Parameters[0] & { dangerouslyForceHydrate?: boolean } -type AnyWritableAtom = WritableAtom +type AnyWritableAtom = WritableAtom type InferAtomTuples = { [K in keyof T]: T[K] extends readonly [infer A, unknown] - ? A extends WritableAtom + ? A extends WritableAtom ? readonly [A, Args[0]] : T[K] : never @@ -43,7 +43,7 @@ export function useHydrateAtoms< for (const [atom, value] of values) { if (!hydratedSet.has(atom) || options?.dangerouslyForceHydrate) { hydratedSet.add(atom) - store.set(atom, value) + store.set(atom, value as never) } } } diff --git a/src/vanilla/store.ts b/src/vanilla/store.ts index 5eb891739a..453e682c2a 100644 --- a/src/vanilla/store.ts +++ b/src/vanilla/store.ts @@ -473,7 +473,7 @@ export const createStore = (): Store => { }, } try { - const valueOrPromise = atom.read(getter, options as any) + const valueOrPromise = atom.read(getter, options as never) return setAtomValueOrPromise(atom, valueOrPromise, nextDependencies, () => controller?.abort(), ) @@ -487,26 +487,6 @@ export const createStore = (): Store => { const readAtom = (atom: Atom): Value => returnAtomValue(readAtomState(atom)) - const addAtom = (atom: AnyAtom): Mounted => { - let mounted = mountedMap.get(atom) - if (!mounted) { - mounted = mountAtom(atom) - } - return mounted - } - - // FIXME doesn't work with mutually dependent atoms - const canUnmountAtom = (atom: AnyAtom, mounted: Mounted) => - !mounted.l.size && - (!mounted.t.size || (mounted.t.size === 1 && mounted.t.has(atom))) - - const delAtom = (atom: AnyAtom): void => { - const mounted = mountedMap.get(atom) - if (mounted && canUnmountAtom(atom, mounted)) { - unmountAtom(atom) - } - } - const recomputeDependents = (atom: AnyAtom): void => { const getDependents = (a: AnyAtom): Dependents => { const dependents = new Set(mountedMap.get(a)?.t) @@ -629,16 +609,19 @@ export const createStore = (): Store => { initialDependent?: AnyAtom, onMountQueue?: (() => void)[], ): Mounted => { + const existingMount = mountedMap.get(atom) + if (existingMount) { + if (initialDependent) { + existingMount.t.add(initialDependent) + } + return existingMount + } + const queue = onMountQueue || [] // mount dependencies before mounting self getAtomState(atom)?.d.forEach((_, a) => { - const aMounted = mountedMap.get(a) - if (aMounted) { - aMounted.t.add(atom) // add dependent - } else { - if (a !== atom) { - mountAtom(a, atom, queue) - } + if (a !== atom) { + mountAtom(a, atom, queue) } }) // recompute atom state @@ -668,9 +651,17 @@ export const createStore = (): Store => { return mounted } - const unmountAtom = (atom: Atom): void => { + // FIXME doesn't work with mutually dependent atoms + const canUnmountAtom = (atom: AnyAtom, mounted: Mounted) => + !mounted.l.size && + (!mounted.t.size || (mounted.t.size === 1 && mounted.t.has(atom))) + + const tryUnmountAtom = (atom: Atom, mounted: Mounted): void => { + if (!canUnmountAtom(atom, mounted)) { + return + } // unmount self - const onUnmount = mountedMap.get(atom)?.u + const onUnmount = mounted.u if (onUnmount) { onUnmount() } @@ -687,12 +678,10 @@ export const createStore = (): Store => { } atomState.d.forEach((_, a) => { if (a !== atom) { - const mounted = mountedMap.get(a) - if (mounted) { - mounted.t.delete(atom) - if (canUnmountAtom(a, mounted)) { - unmountAtom(a) - } + const mountedDep = mountedMap.get(a) + if (mountedDep) { + mountedDep.t.delete(atom) + tryUnmountAtom(a, mountedDep) } } }) @@ -721,20 +710,12 @@ export const createStore = (): Store => { } }) depSet.forEach((a) => { - const mounted = mountedMap.get(a) - if (mounted) { - mounted.t.add(atom) // add to dependents - } else if (mountedMap.has(atom)) { - // we mount dependencies only when atom is already mounted - // Note: we should revisit this when you find other issues - // https://github.com/pmndrs/jotai/issues/942 - mountAtom(a, atom) - } + mountAtom(a, atom) }) maybeUnmountAtomSet.forEach((a) => { const mounted = mountedMap.get(a) - if (mounted && canUnmountAtom(a, mounted)) { - unmountAtom(a) + if (mounted) { + tryUnmountAtom(a, mounted) } }) } @@ -798,7 +779,7 @@ export const createStore = (): Store => { } const subscribeAtom = (atom: AnyAtom, listener: () => void) => { - const mounted = addAtom(atom) + const mounted = mountAtom(atom) const flushed = flushPending([atom]) const listeners = mounted.l listeners.add(listener) @@ -809,7 +790,7 @@ export const createStore = (): Store => { } return () => { listeners.delete(listener) - delAtom(atom) + tryUnmountAtom(atom, mounted) if (import.meta.env?.MODE !== 'production') { // devtools uses this to detect if it _can_ unmount or not storeListenersRev2.forEach((l) => l({ type: 'unsub' })) @@ -859,25 +840,17 @@ export const createStore = (): Store => { let defaultStore: Store | undefined -if (import.meta.env?.MODE !== 'production') { - if (typeof (globalThis as any).__NUMBER_OF_JOTAI_INSTANCES__ === 'number') { - ++(globalThis as any).__NUMBER_OF_JOTAI_INSTANCES__ - } else { - ;(globalThis as any).__NUMBER_OF_JOTAI_INSTANCES__ = 1 - } -} - export const getDefaultStore = (): Store => { if (!defaultStore) { - if ( - import.meta.env?.MODE !== 'production' && - (globalThis as any).__NUMBER_OF_JOTAI_INSTANCES__ !== 1 - ) { - console.warn( - 'Detected multiple Jotai instances. It may cause unexpected behavior with the default store. https://github.com/pmndrs/jotai/discussions/2044', - ) - } defaultStore = createStore() + if (import.meta.env?.MODE !== 'production') { + ;(globalThis as any).__JOTAI_DEFAULT_STORE__ ||= defaultStore + if ((globalThis as any).__JOTAI_DEFAULT_STORE__ !== defaultStore) { + console.warn( + 'Detected multiple Jotai instances. It may cause unexpected behavior with the default store. https://github.com/pmndrs/jotai/discussions/2044', + ) + } + } } return defaultStore } diff --git a/src/vanilla/typeUtils.ts b/src/vanilla/typeUtils.ts index 96344e3925..dbec0c1887 100644 --- a/src/vanilla/typeUtils.ts +++ b/src/vanilla/typeUtils.ts @@ -9,9 +9,13 @@ export type ExtractAtomValue = AtomType extends Atom ? Value : never export type ExtractAtomArgs = - AtomType extends WritableAtom ? Args : never + AtomType extends WritableAtom + ? Args + : never export type ExtractAtomResult = - AtomType extends WritableAtom ? Result : never + AtomType extends WritableAtom + ? Result + : never export type SetStateAction = ExtractAtomArgs>[0] diff --git a/src/vanilla/utils/atomWithReducer.ts b/src/vanilla/utils/atomWithReducer.ts index 5799fb2f2a..d7c1ee478f 100644 --- a/src/vanilla/utils/atomWithReducer.ts +++ b/src/vanilla/utils/atomWithReducer.ts @@ -15,7 +15,7 @@ export function atomWithReducer( initialValue: Value, reducer: (value: Value, action: Action) => Value, ) { - return atom(initialValue, function (this: any, get, set, action: Action) { + return atom(initialValue, function (this: never, get, set, action: Action) { set(this, reducer(get(this), action)) }) } diff --git a/src/vanilla/utils/atomWithRefresh.ts b/src/vanilla/utils/atomWithRefresh.ts index 679d7208b7..626f1a4034 100644 --- a/src/vanilla/utils/atomWithRefresh.ts +++ b/src/vanilla/utils/atomWithRefresh.ts @@ -32,7 +32,7 @@ export function atomWithRefresh( return atom( (get, options) => { get(refreshAtom) - return read(get, options as any) + return read(get, options as never) }, (get, set, ...args: Args) => { if (args.length === 0) { diff --git a/src/vanilla/utils/atomWithStorage.ts b/src/vanilla/utils/atomWithStorage.ts index 7b044f41dc..038c76feaf 100644 --- a/src/vanilla/utils/atomWithStorage.ts +++ b/src/vanilla/utils/atomWithStorage.ts @@ -73,7 +73,7 @@ export function withStorageValidator( return validate(value) }, } - return storage as any // FIXME better way to type this? + return storage } } @@ -113,7 +113,7 @@ export function createJSONStorage( options?: JsonStorageOptions, ): AsyncStorage | SyncStorage { let lastStr: string | undefined - let lastValue: any + let lastValue: Value const storage: AsyncStorage | SyncStorage = { getItem: (key, initialValue) => { const parse = (str: string | null) => { @@ -130,9 +130,9 @@ export function createJSONStorage( } const str = getStringStorage()?.getItem(key) ?? null if (isPromiseLike(str)) { - return str.then(parse) + return str.then(parse) as never } - return parse(str) + return parse(str) as never }, setItem: (key, newValue) => getStringStorage()?.setItem( @@ -197,7 +197,7 @@ export function atomWithStorage( | SyncStorage | AsyncStorage = defaultStorage as SyncStorage, options?: { getOnInit?: boolean }, -): any { +) { const getOnInit = options?.getOnInit const baseAtom = atom( getOnInit @@ -244,5 +244,5 @@ export function atomWithStorage( }, ) - return anAtom + return anAtom as never } diff --git a/src/vanilla/utils/freezeAtom.ts b/src/vanilla/utils/freezeAtom.ts index 236ffc242f..e779c2d4aa 100644 --- a/src/vanilla/utils/freezeAtom.ts +++ b/src/vanilla/utils/freezeAtom.ts @@ -5,33 +5,33 @@ const cache1 = new WeakMap() const memo1 = (create: () => T, dep1: object): T => (cache1.has(dep1) ? cache1 : cache1.set(dep1, create())).get(dep1) -const deepFreeze = (obj: any) => { +const deepFreeze = (obj: unknown) => { if (typeof obj !== 'object' || obj === null) return Object.freeze(obj) const propNames = Object.getOwnPropertyNames(obj) for (const name of propNames) { - const value = obj[name] + const value = (obj as never)[name] deepFreeze(value) } return obj } -export function freezeAtom>( +export function freezeAtom>( anAtom: AtomType, ): AtomType { return memo1(() => { - const frozenAtom: any = atom( + const frozenAtom = atom( (get) => deepFreeze(get(anAtom)), - (_get, set, arg) => set(anAtom as any, arg), + (_get, set, arg) => set(anAtom as never, arg), ) - return frozenAtom + return frozenAtom as never }, anAtom) } export function freezeAtomCreator< - CreateAtom extends (...params: any[]) => Atom, + CreateAtom extends (...params: never[]) => Atom, >(createAtom: CreateAtom): CreateAtom { - return ((...params: any[]) => { + return ((...params: never[]) => { const anAtom = createAtom(...params) const origRead = anAtom.read anAtom.read = function (get, options) { diff --git a/src/vanilla/utils/splitAtom.ts b/src/vanilla/utils/splitAtom.ts index 69bcca4c89..7eec2d4ff3 100644 --- a/src/vanilla/utils/splitAtom.ts +++ b/src/vanilla/utils/splitAtom.ts @@ -22,7 +22,7 @@ const isWritable = ( ): atom is WritableAtom => !!(atom as WritableAtom).write -const isFunction = (x: T): x is T & ((...args: any[]) => any) => +const isFunction = (x: T): x is T & ((...args: never[]) => unknown) => typeof x === 'function' type SplitAtomAction = @@ -94,7 +94,7 @@ export function splitAtom( } throw new Error('splitAtom: index out of bounds for read') } - return currArr[index] as Item + return currArr[index]! } const write = ( get: Getter, @@ -109,7 +109,7 @@ export function splitAtom( throw new Error('splitAtom: index out of bounds for write') } const nextItem = isFunction(update) - ? update(arr[index] as Item) + ? (update as (prev: Item) => Item)(arr[index]!) : update if (!Object.is(arr[index], nextItem)) { set(arrAtom as WritableAtom, [ @@ -190,13 +190,13 @@ export function splitAtom( set(arrAtom as WritableAtom, [ ...arr.slice(0, index1), ...arr.slice(index1 + 1, index2), - arr[index1] as Item, + arr[index1]!, ...arr.slice(index2), ]) } else { set(arrAtom as WritableAtom, [ ...arr.slice(0, index2), - arr[index1] as Item, + arr[index1]!, ...arr.slice(index2, index1), ...arr.slice(index1 + 1), ]) diff --git a/src/vanilla/utils/unwrap.ts b/src/vanilla/utils/unwrap.ts index c83e4a6f50..fe6269636d 100644 --- a/src/vanilla/utils/unwrap.ts +++ b/src/vanilla/utils/unwrap.ts @@ -39,7 +39,7 @@ export function unwrap( export function unwrap( anAtom: WritableAtom | Atom, - fallback: (prev?: Awaited) => PendingValue = defaultFallback as any, + fallback: (prev?: Awaited) => PendingValue = defaultFallback as never, ) { return memo2( () => {