Skip to content

Commit f7c60c1

Browse files
authored
fix(utils): unstable_unwrap to support writable atom (#1997)
* fix(utils): unwrap to support writable atom * fix types * fix type test * fix type again
1 parent 39059bb commit f7c60c1

File tree

3 files changed

+54
-10
lines changed

3 files changed

+54
-10
lines changed

src/vanilla/utils/unwrap.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ const memo2 = <T>(create: () => T, dep1: object, dep2: object): T => {
1111

1212
const defaultFallback = () => undefined
1313

14+
export function unwrap<Value, Args extends unknown[], Result>(
15+
anAtom: WritableAtom<Value, Args, Result>
16+
): WritableAtom<Awaited<Value> | undefined, Args, Result>
17+
18+
export function unwrap<Value, Args extends unknown[], Result, PendingValue>(
19+
anAtom: WritableAtom<Value, Args, Result>,
20+
fallback: (prev?: Awaited<Value>) => PendingValue
21+
): WritableAtom<Awaited<Value> | PendingValue, Args, Result>
22+
1423
export function unwrap<Value>(
1524
anAtom: Atom<Value>
1625
): Atom<Awaited<Value> | undefined>
@@ -20,18 +29,18 @@ export function unwrap<Value, PendingValue>(
2029
fallback: (prev?: Awaited<Value>) => PendingValue
2130
): Atom<Awaited<Value> | PendingValue>
2231

23-
export function unwrap<Value, PendingValue>(
24-
anAtom: Atom<Value>,
32+
export function unwrap<Value, Args extends unknown[], Result, PendingValue>(
33+
anAtom: WritableAtom<Value, Args, Result> | Atom<Value>,
2534
fallback: (prev?: Awaited<Value>) => PendingValue = defaultFallback as any
2635
) {
2736
return memo2(
2837
() => {
29-
type PromiseAndValue = { readonly p?: Promise<Value> } & (
38+
type PromiseAndValue = { readonly p?: Promise<unknown> } & (
3039
| { readonly v: Awaited<Value> }
3140
| { readonly f: PendingValue }
3241
)
33-
const promiseErrorCache = new WeakMap<Promise<Value>, unknown>()
34-
const promiseResultCache = new WeakMap<Promise<Value>, Awaited<Value>>()
42+
const promiseErrorCache = new WeakMap<Promise<unknown>, unknown>()
43+
const promiseResultCache = new WeakMap<Promise<unknown>, Awaited<Value>>()
3544
const refreshAtom = atom(0)
3645

3746
if (import.meta.env?.MODE !== 'production') {
@@ -89,7 +98,7 @@ export function unwrap<Value, PendingValue>(
8998
return state.v
9099
}
91100
return state.f
92-
})
101+
}, (anAtom as WritableAtom<Value, unknown[], unknown>).write)
93102
},
94103
anAtom,
95104
fallback

tests/vanilla/utils/types.test.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expectType } from 'ts-expect'
22
import type { TypeEqual } from 'ts-expect'
33
import { it } from 'vitest'
44
import { atom } from 'jotai/vanilla'
5-
import type { Atom } from 'jotai/vanilla'
5+
import type { Atom, SetStateAction, WritableAtom } from 'jotai/vanilla'
66
import { selectAtom, unstable_unwrap as unwrap } from 'jotai/vanilla/utils'
77

88
it('selectAtom() should return the correct types', () => {
@@ -26,13 +26,28 @@ it('unwrap() should return the correct types', () => {
2626
const getFallbackValue = () => -1
2727
const syncAtom = atom(0)
2828
const syncUnwrappedAtom = unwrap(syncAtom, getFallbackValue)
29-
expectType<TypeEqual<Atom<number>, typeof syncUnwrappedAtom>>(true)
29+
expectType<
30+
TypeEqual<
31+
WritableAtom<number, [SetStateAction<number>], void>,
32+
typeof syncUnwrappedAtom
33+
>
34+
>(true)
3035

3136
const asyncAtom = atom(Promise.resolve(0))
3237
const asyncUnwrappedAtom = unwrap(asyncAtom, getFallbackValue)
33-
expectType<TypeEqual<Atom<number>, typeof asyncUnwrappedAtom>>(true)
38+
expectType<
39+
TypeEqual<
40+
WritableAtom<number, [SetStateAction<Promise<number>>], void>,
41+
typeof asyncUnwrappedAtom
42+
>
43+
>(true)
3444

3545
const maybeAsyncAtom = atom(Promise.resolve(0) as number | Promise<number>)
3646
const maybeAsyncUnwrappedAtom = unwrap(maybeAsyncAtom, getFallbackValue)
37-
expectType<TypeEqual<Atom<number>, typeof maybeAsyncUnwrappedAtom>>(true)
47+
expectType<
48+
TypeEqual<
49+
WritableAtom<number, [SetStateAction<number | Promise<number>>], void>,
50+
typeof maybeAsyncUnwrappedAtom
51+
>
52+
>(true)
3853
})

tests/vanilla/utils/unwrap.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,24 @@ describe('unwrap', () => {
9898
store.set(countAtom, 3)
9999
expect(store.get(syncAtom)).toBe(3)
100100
})
101+
102+
it('should unwrap an async writable atom', async () => {
103+
const store = createStore()
104+
const asyncAtom = atom(Promise.resolve(1))
105+
const syncAtom = unwrap(asyncAtom, (prev?: number) => prev ?? 0)
106+
107+
expect(store.get(syncAtom)).toBe(0)
108+
await new Promise((r) => setTimeout(r)) // wait for a tick
109+
expect(store.get(syncAtom)).toBe(1)
110+
111+
store.set(syncAtom, Promise.resolve(2))
112+
expect(store.get(syncAtom)).toBe(1)
113+
await new Promise((r) => setTimeout(r)) // wait for a tick
114+
expect(store.get(syncAtom)).toBe(2)
115+
116+
store.set(syncAtom, Promise.resolve(3))
117+
expect(store.get(syncAtom)).toBe(2)
118+
await new Promise((r) => setTimeout(r)) // wait for a tick
119+
expect(store.get(syncAtom)).toBe(3)
120+
})
101121
})

0 commit comments

Comments
 (0)