Skip to content

Commit

Permalink
[v2] breaking: do not throw promises (#813)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
dai-shi authored Nov 12, 2023
1 parent cdc9c30 commit 2c33752
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 152 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.0.3",
"proxy-memoize": "^2.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "18.3.0-canary-c47c306a7-20231109",
"react-dom": "18.3.0-canary-c47c306a7-20231109",
"redux": "^4.2.1",
"rollup": "^4.2.0",
"rollup-plugin-esbuild": "^6.1.0",
Expand Down
15 changes: 3 additions & 12 deletions src/react.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
/// <reference types="react/experimental" />

import ReactExports, {
useCallback,
useDebugValue,
useEffect,
useMemo,
useRef,
} from 'react'
import { useCallback, useDebugValue, useEffect, useMemo, useRef } from 'react'
import {
affectedToPathList,
createProxy as createProxyToCompare,
Expand All @@ -21,7 +13,6 @@ import useSyncExternalStoreExports from 'use-sync-external-store/shim'
import { snapshot, subscribe } from './vanilla.ts'
import type { INTERNAL_Snapshot as Snapshot } from './vanilla.ts'

const { use } = ReactExports
const { useSyncExternalStore } = useSyncExternalStoreExports

const useAffectedDebugValue = (
Expand Down Expand Up @@ -133,7 +124,7 @@ export function useSnapshot<T extends object>(
[proxyObject, notifyInSync]
),
() => {
const nextSnapshot = snapshot(proxyObject, use)
const nextSnapshot = snapshot(proxyObject)
try {
if (
!inRender &&
Expand All @@ -154,7 +145,7 @@ export function useSnapshot<T extends object>(
}
return nextSnapshot
},
() => snapshot(proxyObject, use)
() => snapshot(proxyObject)
)
inRender = false
const currAffected = new WeakMap()
Expand Down
47 changes: 5 additions & 42 deletions src/vanilla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ type SnapshotIgnore =

type Snapshot<T> = T extends SnapshotIgnore
? T
: T extends Promise<unknown>
? Awaited<T>
: T extends object
? { readonly [K in keyof T]: Snapshot<T[K]> }
: T
Expand All @@ -45,13 +43,7 @@ type Snapshot<T> = T extends SnapshotIgnore
*/
export type INTERNAL_Snapshot<T> = Snapshot<T>

type HandlePromise = <P extends Promise<any>>(promise: P) => Awaited<P>

type CreateSnapshot = <T extends object>(
target: T,
version: number,
handlePromise?: HandlePromise
) => T
type CreateSnapshot = <T extends object>(target: T, version: number) => T

type RemoveListener = () => void
type AddListener = (listener: Listener) => RemoveListener
Expand Down Expand Up @@ -86,29 +78,11 @@ const buildProxyFunction = (
!(x instanceof RegExp) &&
!(x instanceof ArrayBuffer),

defaultHandlePromise = <P extends Promise<any>>(
promise: P & {
status?: 'pending' | 'fulfilled' | 'rejected'
value?: Awaited<P>
reason?: unknown
}
) => {
switch (promise.status) {
case 'fulfilled':
return promise.value as Awaited<P>
case 'rejected':
throw promise.reason
default:
throw promise
}
},

snapCache = new WeakMap<object, [version: number, snap: unknown]>(),

createSnapshot: CreateSnapshot = <T extends object>(
target: T,
version: number,
handlePromise: HandlePromise = defaultHandlePromise
version: number
): T => {
const cache = snapCache.get(target)
if (cache?.[0] === version) {
Expand Down Expand Up @@ -138,18 +112,11 @@ const buildProxyFunction = (
}
if (refSet.has(value as object)) {
markToTrack(value as object, false) // mark not to track
} else if (value instanceof Promise) {
delete desc.value
desc.get = () => handlePromise(value)
} else if (proxyStateMap.has(value as object)) {
const [target, ensureVersion] = proxyStateMap.get(
value as object
) as ProxyState
desc.value = createSnapshot(
target,
ensureVersion(),
handlePromise
) as Snapshot<T>
desc.value = createSnapshot(target, ensureVersion()) as Snapshot<T>
}
Object.defineProperty(snap, key, desc)
})
Expand Down Expand Up @@ -337,7 +304,6 @@ const buildProxyFunction = (
objectIs,
newProxy,
canProxy,
defaultHandlePromise,
snapCache,
createSnapshot,
proxyCache,
Expand Down Expand Up @@ -391,16 +357,13 @@ export function subscribe<T extends object>(
}
}

export function snapshot<T extends object>(
proxyObject: T,
handlePromise?: HandlePromise
): Snapshot<T> {
export function snapshot<T extends object>(proxyObject: T): Snapshot<T> {
const proxyState = proxyStateMap.get(proxyObject as object)
if (import.meta.env?.MODE !== 'production' && !proxyState) {
console.warn('Please use proxy object')
}
const [target, ensureVersion, createSnapshot] = proxyState as ProxyState
return createSnapshot(target, ensureVersion(), handlePromise) as Snapshot<T>
return createSnapshot(target, ensureVersion()) as Snapshot<T>
}

export function ref<T extends object>(obj: T): T & AsRef {
Expand Down
100 changes: 54 additions & 46 deletions tests/async.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { StrictMode, Suspense } from 'react'
/// <reference types="react/canary" />

import ReactExports, { StrictMode, Suspense } from 'react'
import { fireEvent, render, waitFor } from '@testing-library/react'
import { it } from 'vitest'
import { proxy, useSnapshot } from 'valtio'
Expand All @@ -8,7 +10,10 @@ const sleep = (ms: number) =>
setTimeout(resolve, ms)
})

it('delayed increment', async () => {
const { use } = ReactExports as any // for TS < 4.3 FIXME later
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)

it.skipIf(typeof use === 'undefined')('delayed increment', async () => {
const state = proxy<any>({ count: 0 })
const delayedIncrement = () => {
const nextCount = state.count + 1
Expand All @@ -19,7 +24,7 @@ it('delayed increment', async () => {
const snap = useSnapshot(state)
return (
<>
<div>count: {snap.count}</div>
<div>count: {use2(snap.count)}</div>
<button onClick={delayedIncrement}>button</button>
</>
)
Expand All @@ -40,7 +45,7 @@ it('delayed increment', async () => {
await findByText('count: 1')
})

it('delayed object', async () => {
it.skipIf(typeof use === 'undefined')('delayed object', async () => {
const state = proxy<any>({ object: { text: 'none' } })
const delayedObject = () => {
state.object = sleep(300).then(() => ({ text: 'hello' }))
Expand All @@ -50,7 +55,7 @@ it('delayed object', async () => {
const snap = useSnapshot(state)
return (
<>
<div>text: {snap.object.text}</div>
<div>text: {use2(snap.object).text}</div>
<button onClick={delayedObject}>button</button>
</>
)
Expand All @@ -71,51 +76,54 @@ it('delayed object', async () => {
await findByText('text: hello')
})

it('delayed object update fulfilled', async () => {
const state = proxy<any>({
object: sleep(300).then(() => ({ text: 'counter', count: 0 })),
})
const updateObject = () => {
state.object = state.object.then((v: any) =>
sleep(300).then(() => ({ ...v, count: v.count + 1 }))
it.skipIf(typeof use === 'undefined')(
'delayed object update fulfilled',
async () => {
const state = proxy<any>({
object: sleep(300).then(() => ({ text: 'counter', count: 0 })),
})
const updateObject = () => {
state.object = state.object.then((v: any) =>
sleep(300).then(() => ({ ...v, count: v.count + 1 }))
)
}

const Counter = () => {
const snap = useSnapshot(state)
return (
<>
<div>text: {use2(snap.object).text}</div>
<div>count: {use2(snap.object).count}</div>
<button onClick={updateObject}>button</button>
</>
)
}

const { getByText, findByText } = render(
<StrictMode>
<Suspense fallback="loading">
<Counter />
</Suspense>
</StrictMode>
)
}

const Counter = () => {
const snap = useSnapshot(state)
return (
<>
<div>text: {snap.object.text}</div>
<div>count: {snap.object.count}</div>
<button onClick={updateObject}>button</button>
</>
)
}
await findByText('loading')
await waitFor(() => {
getByText('text: counter')
getByText('count: 0')
})

const { getByText, findByText } = render(
<StrictMode>
<Suspense fallback="loading">
<Counter />
</Suspense>
</StrictMode>
)

await findByText('loading')
await waitFor(() => {
getByText('text: counter')
getByText('count: 0')
})
fireEvent.click(getByText('button'))

fireEvent.click(getByText('button'))

await findByText('loading')
await waitFor(() => {
getByText('text: counter')
getByText('count: 1')
})
})
await findByText('loading')
await waitFor(() => {
getByText('text: counter')
getByText('count: 1')
})
}
)

it('delayed falsy value', async () => {
it.skipIf(typeof use === 'undefined')('delayed falsy value', async () => {
const state = proxy<any>({ value: true })
const delayedValue = () => {
state.value = sleep(300).then(() => null)
Expand All @@ -125,7 +133,7 @@ it('delayed falsy value', async () => {
const snap = useSnapshot(state)
return (
<>
<div>value: {String(snap.value)}</div>
<div>value: {String(use2(snap.value))}</div>
<button onClick={delayedValue}>button</button>
</>
)
Expand Down
11 changes: 8 additions & 3 deletions tests/computed.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { StrictMode, Suspense } from 'react'
/// <reference types="react/canary" />

import ReactExports, { StrictMode, Suspense } from 'react'
import { fireEvent, render } from '@testing-library/react'
import { memoize } from 'proxy-memoize'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
import { addComputed, proxyWithComputed, subscribeKey } from 'valtio/utils'

const { use } = ReactExports as any // for TS < 4.3 FIXME later
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)

const consoleWarn = console.warn
beforeEach(() => {
console.warn = vi.fn((message: string) => {
Expand Down Expand Up @@ -201,7 +206,7 @@ describe('DEPRECATED addComputed', () => {
expect(callback).toBeCalledTimes(2)
})

it('async addComputed', async () => {
it.skipIf(typeof use === 'undefined')('async addComputed', async () => {
const state = proxy({ count: 0 })
addComputed(state, {
delayedCount: async (snap) => {
Expand All @@ -217,7 +222,7 @@ describe('DEPRECATED addComputed', () => {
return (
<>
<div>
count: {snap.count}, delayedCount: {snap.delayedCount}
count: {snap.count}, delayedCount: {use2(snap.delayedCount)}
</div>
<button onClick={() => ++state.count}>button</button>
</>
Expand Down
11 changes: 8 additions & 3 deletions tests/derive.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { StrictMode, Suspense, useEffect, useRef } from 'react'
/// <reference types="react/canary" />

import ReactExports, { StrictMode, Suspense, useEffect, useRef } from 'react'
import { fireEvent, render } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio'
Expand All @@ -11,6 +13,9 @@ const sleep = (ms: number) =>
setTimeout(resolve, ms)
})

const { use } = ReactExports as any // for TS < 4.3 FIXME later
const use2 = (x: any) => (x instanceof Promise ? use(x) : x)

it('basic derive', async () => {
const computeDouble = vi.fn((x: number) => x * 2)
const state = proxy({
Expand Down Expand Up @@ -149,7 +154,7 @@ it('derive with two dependencies', async () => {
expect(callback).toBeCalledTimes(2)
})

it('async derive', async () => {
it.skipIf(typeof use === 'undefined')('async derive', async () => {
const state = proxy({ count: 0 })
derive(
{
Expand All @@ -168,7 +173,7 @@ it('async derive', async () => {
return (
<>
<div>
count: {snap.count}, delayedCount: {snap.delayedCount}
count: {snap.count}, delayedCount: {use2(snap.delayedCount)}
</div>
<button onClick={() => ++state.count}>button</button>
</>
Expand Down
Loading

0 comments on commit 2c33752

Please sign in to comment.