Skip to content

Commit 2c33752

Browse files
authored
[v2] breaking: do not throw promises (#813)
* [v2] breaking: do not throw promises * use use * fix CI hopefully * fix CI hopefully 2 * fix CI hopefully 3 * fix CI hopefully 4 * fix CI hopefully 5 * any type for simplicity
1 parent cdc9c30 commit 2c33752

File tree

8 files changed

+93
-152
lines changed

8 files changed

+93
-152
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@
158158
"postinstall-postinstall": "^2.1.0",
159159
"prettier": "^3.0.3",
160160
"proxy-memoize": "^2.0.4",
161-
"react": "^18.2.0",
162-
"react-dom": "^18.2.0",
161+
"react": "18.3.0-canary-c47c306a7-20231109",
162+
"react-dom": "18.3.0-canary-c47c306a7-20231109",
163163
"redux": "^4.2.1",
164164
"rollup": "^4.2.0",
165165
"rollup-plugin-esbuild": "^6.1.0",

src/react.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
/// <reference types="react/experimental" />
2-
3-
import ReactExports, {
4-
useCallback,
5-
useDebugValue,
6-
useEffect,
7-
useMemo,
8-
useRef,
9-
} from 'react'
1+
import { useCallback, useDebugValue, useEffect, useMemo, useRef } from 'react'
102
import {
113
affectedToPathList,
124
createProxy as createProxyToCompare,
@@ -21,7 +13,6 @@ import useSyncExternalStoreExports from 'use-sync-external-store/shim'
2113
import { snapshot, subscribe } from './vanilla.ts'
2214
import type { INTERNAL_Snapshot as Snapshot } from './vanilla.ts'
2315

24-
const { use } = ReactExports
2516
const { useSyncExternalStore } = useSyncExternalStoreExports
2617

2718
const useAffectedDebugValue = (
@@ -133,7 +124,7 @@ export function useSnapshot<T extends object>(
133124
[proxyObject, notifyInSync]
134125
),
135126
() => {
136-
const nextSnapshot = snapshot(proxyObject, use)
127+
const nextSnapshot = snapshot(proxyObject)
137128
try {
138129
if (
139130
!inRender &&
@@ -154,7 +145,7 @@ export function useSnapshot<T extends object>(
154145
}
155146
return nextSnapshot
156147
},
157-
() => snapshot(proxyObject, use)
148+
() => snapshot(proxyObject)
158149
)
159150
inRender = false
160151
const currAffected = new WeakMap()

src/vanilla.ts

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ type SnapshotIgnore =
3333

3434
type Snapshot<T> = T extends SnapshotIgnore
3535
? T
36-
: T extends Promise<unknown>
37-
? Awaited<T>
3836
: T extends object
3937
? { readonly [K in keyof T]: Snapshot<T[K]> }
4038
: T
@@ -45,13 +43,7 @@ type Snapshot<T> = T extends SnapshotIgnore
4543
*/
4644
export type INTERNAL_Snapshot<T> = Snapshot<T>
4745

48-
type HandlePromise = <P extends Promise<any>>(promise: P) => Awaited<P>
49-
50-
type CreateSnapshot = <T extends object>(
51-
target: T,
52-
version: number,
53-
handlePromise?: HandlePromise
54-
) => T
46+
type CreateSnapshot = <T extends object>(target: T, version: number) => T
5547

5648
type RemoveListener = () => void
5749
type AddListener = (listener: Listener) => RemoveListener
@@ -86,29 +78,11 @@ const buildProxyFunction = (
8678
!(x instanceof RegExp) &&
8779
!(x instanceof ArrayBuffer),
8880

89-
defaultHandlePromise = <P extends Promise<any>>(
90-
promise: P & {
91-
status?: 'pending' | 'fulfilled' | 'rejected'
92-
value?: Awaited<P>
93-
reason?: unknown
94-
}
95-
) => {
96-
switch (promise.status) {
97-
case 'fulfilled':
98-
return promise.value as Awaited<P>
99-
case 'rejected':
100-
throw promise.reason
101-
default:
102-
throw promise
103-
}
104-
},
105-
10681
snapCache = new WeakMap<object, [version: number, snap: unknown]>(),
10782

10883
createSnapshot: CreateSnapshot = <T extends object>(
10984
target: T,
110-
version: number,
111-
handlePromise: HandlePromise = defaultHandlePromise
85+
version: number
11286
): T => {
11387
const cache = snapCache.get(target)
11488
if (cache?.[0] === version) {
@@ -138,18 +112,11 @@ const buildProxyFunction = (
138112
}
139113
if (refSet.has(value as object)) {
140114
markToTrack(value as object, false) // mark not to track
141-
} else if (value instanceof Promise) {
142-
delete desc.value
143-
desc.get = () => handlePromise(value)
144115
} else if (proxyStateMap.has(value as object)) {
145116
const [target, ensureVersion] = proxyStateMap.get(
146117
value as object
147118
) as ProxyState
148-
desc.value = createSnapshot(
149-
target,
150-
ensureVersion(),
151-
handlePromise
152-
) as Snapshot<T>
119+
desc.value = createSnapshot(target, ensureVersion()) as Snapshot<T>
153120
}
154121
Object.defineProperty(snap, key, desc)
155122
})
@@ -337,7 +304,6 @@ const buildProxyFunction = (
337304
objectIs,
338305
newProxy,
339306
canProxy,
340-
defaultHandlePromise,
341307
snapCache,
342308
createSnapshot,
343309
proxyCache,
@@ -391,16 +357,13 @@ export function subscribe<T extends object>(
391357
}
392358
}
393359

394-
export function snapshot<T extends object>(
395-
proxyObject: T,
396-
handlePromise?: HandlePromise
397-
): Snapshot<T> {
360+
export function snapshot<T extends object>(proxyObject: T): Snapshot<T> {
398361
const proxyState = proxyStateMap.get(proxyObject as object)
399362
if (import.meta.env?.MODE !== 'production' && !proxyState) {
400363
console.warn('Please use proxy object')
401364
}
402365
const [target, ensureVersion, createSnapshot] = proxyState as ProxyState
403-
return createSnapshot(target, ensureVersion(), handlePromise) as Snapshot<T>
366+
return createSnapshot(target, ensureVersion()) as Snapshot<T>
404367
}
405368

406369
export function ref<T extends object>(obj: T): T & AsRef {

tests/async.test.tsx

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { StrictMode, Suspense } from 'react'
1+
/// <reference types="react/canary" />
2+
3+
import ReactExports, { StrictMode, Suspense } from 'react'
24
import { fireEvent, render, waitFor } from '@testing-library/react'
35
import { it } from 'vitest'
46
import { proxy, useSnapshot } from 'valtio'
@@ -8,7 +10,10 @@ const sleep = (ms: number) =>
810
setTimeout(resolve, ms)
911
})
1012

11-
it('delayed increment', async () => {
13+
const { use } = ReactExports as any // for TS < 4.3 FIXME later
14+
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)
15+
16+
it.skipIf(typeof use === 'undefined')('delayed increment', async () => {
1217
const state = proxy<any>({ count: 0 })
1318
const delayedIncrement = () => {
1419
const nextCount = state.count + 1
@@ -19,7 +24,7 @@ it('delayed increment', async () => {
1924
const snap = useSnapshot(state)
2025
return (
2126
<>
22-
<div>count: {snap.count}</div>
27+
<div>count: {use2(snap.count)}</div>
2328
<button onClick={delayedIncrement}>button</button>
2429
</>
2530
)
@@ -40,7 +45,7 @@ it('delayed increment', async () => {
4045
await findByText('count: 1')
4146
})
4247

43-
it('delayed object', async () => {
48+
it.skipIf(typeof use === 'undefined')('delayed object', async () => {
4449
const state = proxy<any>({ object: { text: 'none' } })
4550
const delayedObject = () => {
4651
state.object = sleep(300).then(() => ({ text: 'hello' }))
@@ -50,7 +55,7 @@ it('delayed object', async () => {
5055
const snap = useSnapshot(state)
5156
return (
5257
<>
53-
<div>text: {snap.object.text}</div>
58+
<div>text: {use2(snap.object).text}</div>
5459
<button onClick={delayedObject}>button</button>
5560
</>
5661
)
@@ -71,51 +76,54 @@ it('delayed object', async () => {
7176
await findByText('text: hello')
7277
})
7378

74-
it('delayed object update fulfilled', async () => {
75-
const state = proxy<any>({
76-
object: sleep(300).then(() => ({ text: 'counter', count: 0 })),
77-
})
78-
const updateObject = () => {
79-
state.object = state.object.then((v: any) =>
80-
sleep(300).then(() => ({ ...v, count: v.count + 1 }))
79+
it.skipIf(typeof use === 'undefined')(
80+
'delayed object update fulfilled',
81+
async () => {
82+
const state = proxy<any>({
83+
object: sleep(300).then(() => ({ text: 'counter', count: 0 })),
84+
})
85+
const updateObject = () => {
86+
state.object = state.object.then((v: any) =>
87+
sleep(300).then(() => ({ ...v, count: v.count + 1 }))
88+
)
89+
}
90+
91+
const Counter = () => {
92+
const snap = useSnapshot(state)
93+
return (
94+
<>
95+
<div>text: {use2(snap.object).text}</div>
96+
<div>count: {use2(snap.object).count}</div>
97+
<button onClick={updateObject}>button</button>
98+
</>
99+
)
100+
}
101+
102+
const { getByText, findByText } = render(
103+
<StrictMode>
104+
<Suspense fallback="loading">
105+
<Counter />
106+
</Suspense>
107+
</StrictMode>
81108
)
82-
}
83109

84-
const Counter = () => {
85-
const snap = useSnapshot(state)
86-
return (
87-
<>
88-
<div>text: {snap.object.text}</div>
89-
<div>count: {snap.object.count}</div>
90-
<button onClick={updateObject}>button</button>
91-
</>
92-
)
93-
}
110+
await findByText('loading')
111+
await waitFor(() => {
112+
getByText('text: counter')
113+
getByText('count: 0')
114+
})
94115

95-
const { getByText, findByText } = render(
96-
<StrictMode>
97-
<Suspense fallback="loading">
98-
<Counter />
99-
</Suspense>
100-
</StrictMode>
101-
)
102-
103-
await findByText('loading')
104-
await waitFor(() => {
105-
getByText('text: counter')
106-
getByText('count: 0')
107-
})
116+
fireEvent.click(getByText('button'))
108117

109-
fireEvent.click(getByText('button'))
110-
111-
await findByText('loading')
112-
await waitFor(() => {
113-
getByText('text: counter')
114-
getByText('count: 1')
115-
})
116-
})
118+
await findByText('loading')
119+
await waitFor(() => {
120+
getByText('text: counter')
121+
getByText('count: 1')
122+
})
123+
}
124+
)
117125

118-
it('delayed falsy value', async () => {
126+
it.skipIf(typeof use === 'undefined')('delayed falsy value', async () => {
119127
const state = proxy<any>({ value: true })
120128
const delayedValue = () => {
121129
state.value = sleep(300).then(() => null)
@@ -125,7 +133,7 @@ it('delayed falsy value', async () => {
125133
const snap = useSnapshot(state)
126134
return (
127135
<>
128-
<div>value: {String(snap.value)}</div>
136+
<div>value: {String(use2(snap.value))}</div>
129137
<button onClick={delayedValue}>button</button>
130138
</>
131139
)

tests/computed.test.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import { StrictMode, Suspense } from 'react'
1+
/// <reference types="react/canary" />
2+
3+
import ReactExports, { StrictMode, Suspense } from 'react'
24
import { fireEvent, render } from '@testing-library/react'
35
import { memoize } from 'proxy-memoize'
46
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
57
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
68
import { addComputed, proxyWithComputed, subscribeKey } from 'valtio/utils'
79

10+
const { use } = ReactExports as any // for TS < 4.3 FIXME later
11+
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)
12+
813
const consoleWarn = console.warn
914
beforeEach(() => {
1015
console.warn = vi.fn((message: string) => {
@@ -201,7 +206,7 @@ describe('DEPRECATED addComputed', () => {
201206
expect(callback).toBeCalledTimes(2)
202207
})
203208

204-
it('async addComputed', async () => {
209+
it.skipIf(typeof use === 'undefined')('async addComputed', async () => {
205210
const state = proxy({ count: 0 })
206211
addComputed(state, {
207212
delayedCount: async (snap) => {
@@ -217,7 +222,7 @@ describe('DEPRECATED addComputed', () => {
217222
return (
218223
<>
219224
<div>
220-
count: {snap.count}, delayedCount: {snap.delayedCount}
225+
count: {snap.count}, delayedCount: {use2(snap.delayedCount)}
221226
</div>
222227
<button onClick={() => ++state.count}>button</button>
223228
</>

tests/derive.test.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { StrictMode, Suspense, useEffect, useRef } from 'react'
1+
/// <reference types="react/canary" />
2+
3+
import ReactExports, { StrictMode, Suspense, useEffect, useRef } from 'react'
24
import { fireEvent, render } from '@testing-library/react'
35
import { describe, expect, it, vi } from 'vitest'
46
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
@@ -11,6 +13,9 @@ const sleep = (ms: number) =>
1113
setTimeout(resolve, ms)
1214
})
1315

16+
const { use } = ReactExports as any // for TS < 4.3 FIXME later
17+
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)
18+
1419
it('basic derive', async () => {
1520
const computeDouble = vi.fn((x: number) => x * 2)
1621
const state = proxy({
@@ -149,7 +154,7 @@ it('derive with two dependencies', async () => {
149154
expect(callback).toBeCalledTimes(2)
150155
})
151156

152-
it('async derive', async () => {
157+
it.skipIf(typeof use === 'undefined')('async derive', async () => {
153158
const state = proxy({ count: 0 })
154159
derive(
155160
{
@@ -168,7 +173,7 @@ it('async derive', async () => {
168173
return (
169174
<>
170175
<div>
171-
count: {snap.count}, delayedCount: {snap.delayedCount}
176+
count: {snap.count}, delayedCount: {use2(snap.delayedCount)}
172177
</div>
173178
<button onClick={() => ++state.count}>button</button>
174179
</>

0 commit comments

Comments
 (0)