Skip to content

Commit

Permalink
fix: swich to using useDeferredValue by default. Add { defer: false }…
Browse files Browse the repository at this point in the history
… support to useSub() and to observer() (effective for any useSub inside) options to not use it.
  • Loading branch information
cray0000 committed Sep 27, 2024
1 parent 477dd51 commit 841dc26
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 24 deletions.
7 changes: 6 additions & 1 deletion packages/teamplay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export { GLOBAL_ROOT_ID } from './orm/Root.js'
export const $ = _getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ })
export default $
export { default as sub } from './orm/sub.js'
export { default as useSub, useAsyncSub, setUseDeferredValue as __setUseDeferredValue } from './react/useSub.js'
export {
default as useSub,
useAsyncSub,
setUseDeferredValue as __setUseDeferredValue,
setDefaultDefer as __setDefaultDefer
} from './react/useSub.js'
export { default as observer } from './react/observer.js'
export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js'
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
Expand Down
6 changes: 6 additions & 0 deletions packages/teamplay/react/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export function useScheduleUpdate () {
return context.scheduleUpdate
}

export function useDefer () {
const context = useContext(ComponentMetaContext)
if (!context) throw Error(ERRORS.useScheduleUpdate)
return context.defer
}

export function useCache (key) {
const context = useContext(ComponentMetaContext)
if (!context) throw Error(ERRORS.useCache)
Expand Down
21 changes: 15 additions & 6 deletions packages/teamplay/react/useSub.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useRef, useDeferredValue } from 'react'
import sub from '../orm/sub.js'
import { useScheduleUpdate, useCache } from './helpers.js'
import { useScheduleUpdate, useCache, useDefer } from './helpers.js'
import executionContextTracker from './executionContextTracker.js'

let TEST_THROTTLING = false

// experimental feature to leverage useDeferredValue() to handle re-subscriptions.
// Currently it does lead to issues with extra rerenders and requires further investigation
let USE_DEFERRED_VALUE = false
let USE_DEFERRED_VALUE = true
// by default we want to defer stuff if possible instead of throwing promises
let DEFAULT_DEFER = true

export function useAsyncSub (signal, params, options) {
return useSub(signal, params, { ...options, async: true })
Expand All @@ -22,12 +24,16 @@ export default function useSub (signal, params, options) {
}

// version of sub() which works as a react hook and throws promise for Suspense
export function useSubDeferred (signal, params, { async = false } = {}) {
export function useSubDeferred (signal, params, { async = false, defer } = {}) {
const $signalRef = useRef() // eslint-disable-line react-hooks/rules-of-hooks
const scheduleUpdate = useScheduleUpdate()
signal = useDeferredValue(signal)
params = useDeferredValue(params ? JSON.stringify(params) : undefined)
params = params != null ? JSON.parse(params) : undefined
const observerDefer = useDefer()
defer ??= observerDefer ?? DEFAULT_DEFER
if (defer) {
signal = useDeferredValue(signal) // eslint-disable-line react-hooks/rules-of-hooks
params = useDeferredValue(params ? JSON.stringify(params) : undefined) // eslint-disable-line react-hooks/rules-of-hooks
params = params != null ? JSON.parse(params) : undefined
}
const promiseOrSignal = params != null ? sub(signal, params) : sub(signal)
// 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
if (promiseOrSignal.then) {
Expand Down Expand Up @@ -97,6 +103,9 @@ export function resetTestThrottling () {
export function setUseDeferredValue (value) {
USE_DEFERRED_VALUE = value
}
export function setDefaultDefer (value) {
DEFAULT_DEFER = value
}

// throttle to simulate slow network
function maybeThrottle (promise) {
Expand Down
2 changes: 2 additions & 0 deletions packages/teamplay/react/wrapIntoSuspense.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function destroyAdm (adm) {
export default function wrapIntoSuspense ({
Component,
forwardRef,
defer,
suspenseProps = DEFAULT_SUSPENSE_PROPS
} = {}) {
if (!suspenseProps?.fallback) throw Error(ERRORS.noFallback)
Expand Down Expand Up @@ -62,6 +63,7 @@ export default function wrapIntoSuspense ({
componentMetaRef.current = {
componentId,
createdAt: Date.now(),
defer,
triggerUpdate: () => adm.onStoreChange?.(),
scheduleUpdate: promise => adm.scheduleUpdate?.(promise),
cache: {
Expand Down
34 changes: 17 additions & 17 deletions packages/teamplay/test_client/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,27 +380,27 @@ describe('useSub() for subscribing to queries', () => {
expect(container.textContent).toBe('John,Jane')

fireEvent.click(container.querySelector('#active'))
expect(renders).toBe(3)
expect(renders).toBe(4)
expect(container.textContent).toBe('John,Jane')
await wait()
expect(renders).toBe(3)
expect(renders).toBe(4)
expect(container.textContent).toBe('John,Jane')
await throttledWait()
expect(renders).toBe(4)
expect(renders).toBe(5)
expect(container.textContent).toBe('John')

await wait()
expect(renders).toBe(4)
expect(renders).toBe(5)

fireEvent.click(container.querySelector('#inactive'))
expect(renders).toBe(5)
expect(renders).toBe(7)
expect(container.textContent).toBe('John')
await throttledWait()
expect(renders).toBe(6)
expect(renders).toBe(8)
expect(container.textContent).toBe('Jane')

await throttledWait()
expect(renders).toBe(6)
expect(renders).toBe(8)
resetTestThrottling()
})
})
Expand Down Expand Up @@ -438,27 +438,27 @@ describe('useAsyncSub()', () => {
expect(container.textContent).toBe('John,Jane')

fireEvent.click(container.querySelector('#active'))
expect(renders).toBe(3)
expect(container.textContent).toBe('John,Jane')
expect(renders).toBe(4)
expect(container.textContent).toBe('Waiting for users to load...')
await wait()
expect(renders).toBe(3)
expect(container.textContent).toBe('John,Jane')
await throttledWait()
expect(renders).toBe(4)
expect(container.textContent).toBe('Waiting for users to load...')
await throttledWait()
expect(renders).toBe(5)
expect(container.textContent).toBe('John')

await wait()
expect(renders).toBe(4)
expect(renders).toBe(5)

fireEvent.click(container.querySelector('#inactive'))
expect(renders).toBe(5)
expect(container.textContent).toBe('John')
expect(renders).toBe(7)
expect(container.textContent).toBe('Waiting for users to load...')
await throttledWait()
expect(renders).toBe(6)
expect(renders).toBe(8)
expect(container.textContent).toBe('Jane')

await throttledWait()
expect(renders).toBe(6)
expect(renders).toBe(8)
resetTestThrottling()
})
})
Expand Down

0 comments on commit 841dc26

Please sign in to comment.