mirror of
https://github.com/re-rxjs/react-rxjs.git
synced 2025-12-08 18:01:51 +00:00
feat(core): default value on bind
This commit is contained in:
parent
09fde4b9ac
commit
ca95e6a80f
@ -9,7 +9,7 @@ import {
|
||||
Subject,
|
||||
} from "rxjs"
|
||||
import { renderHook, act as actHook } from "@testing-library/react-hooks"
|
||||
import { switchMap, delay, take, catchError } from "rxjs/operators"
|
||||
import { switchMap, delay, take, catchError, map } from "rxjs/operators"
|
||||
import { FC, Suspense, useState } from "react"
|
||||
import React from "react"
|
||||
import {
|
||||
@ -426,6 +426,53 @@ describe("connectFactoryObservable", () => {
|
||||
|
||||
unmount()
|
||||
})
|
||||
|
||||
it("if the observable hasn't emitted and a defaultValue is provided, it does not start suspense", () => {
|
||||
const number$ = new Subject<number>()
|
||||
const [useNumber] = bind(
|
||||
(id: number) => number$.pipe(map((x) => x + id)),
|
||||
0,
|
||||
)
|
||||
|
||||
const { result, unmount } = renderHook(() => useNumber(5))
|
||||
|
||||
expect(result.current).toBe(0)
|
||||
|
||||
actHook(() => {
|
||||
number$.next(5)
|
||||
})
|
||||
|
||||
expect(result.current).toBe(10)
|
||||
|
||||
unmount()
|
||||
})
|
||||
|
||||
it("when a defaultValue is provided, the first subscription happens once the component is mounted", () => {
|
||||
let nTopSubscriptions = 0
|
||||
|
||||
const [useNTopSubscriptions] = bind(
|
||||
(id: number) =>
|
||||
defer(() => {
|
||||
return of(++nTopSubscriptions + id)
|
||||
}),
|
||||
1,
|
||||
)
|
||||
|
||||
const { result, rerender, unmount } = renderHook(() =>
|
||||
useNTopSubscriptions(0),
|
||||
)
|
||||
|
||||
expect(result.current).toBe(1)
|
||||
|
||||
actHook(() => {
|
||||
rerender()
|
||||
})
|
||||
|
||||
expect(result.current).toBe(1)
|
||||
expect(nTopSubscriptions).toBe(1)
|
||||
|
||||
unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe("observable", () => {
|
||||
|
||||
@ -3,6 +3,7 @@ import shareLatest from "../internal/share-latest"
|
||||
import reactEnhancer from "../internal/react-enhancer"
|
||||
import { BehaviorObservable } from "../internal/BehaviorObservable"
|
||||
import { useObservable } from "../internal/useObservable"
|
||||
import { EMPTY_VALUE } from "../internal/empty-value"
|
||||
import { SUSPENSE } from "../SUSPENSE"
|
||||
|
||||
/**
|
||||
@ -26,6 +27,7 @@ import { SUSPENSE } from "../SUSPENSE"
|
||||
*/
|
||||
export default function connectFactoryObservable<A extends [], O>(
|
||||
getObservable: (...args: A) => Observable<O>,
|
||||
defaultValue: O = EMPTY_VALUE,
|
||||
): [
|
||||
(...args: A) => Exclude<O, typeof SUSPENSE>,
|
||||
(...args: A) => Observable<O>,
|
||||
@ -67,7 +69,7 @@ export default function connectFactoryObservable<A extends [], O>(
|
||||
return source$.subscribe(subscriber)
|
||||
}) as BehaviorObservable<O>
|
||||
publicShared$.getValue = sharedObservable$.getValue
|
||||
const reactGetValue = reactEnhancer(publicShared$)
|
||||
const reactGetValue = reactEnhancer(publicShared$, defaultValue)
|
||||
|
||||
const result: [BehaviorObservable<O>, () => O] = [
|
||||
publicShared$,
|
||||
|
||||
@ -508,4 +508,45 @@ describe("connectObservable", () => {
|
||||
expect(screen.queryByText("Loading...")).toBeNull()
|
||||
expect(screen.queryByText("Hello")).not.toBeNull()
|
||||
})
|
||||
|
||||
it("if the observable hasn't emitted and a defaultValue is provided, it does not start suspense", () => {
|
||||
const number$ = new Subject<number>()
|
||||
const [useNumber] = bind(number$, 0)
|
||||
|
||||
const { result, unmount } = renderHook(() => useNumber())
|
||||
|
||||
expect(result.current).toBe(0)
|
||||
|
||||
act(() => {
|
||||
number$.next(5)
|
||||
})
|
||||
|
||||
expect(result.current).toBe(5)
|
||||
|
||||
unmount()
|
||||
})
|
||||
|
||||
it("when a defaultValue is provided, the first subscription happens once the component is mounted", () => {
|
||||
let nTopSubscriptions = 0
|
||||
|
||||
const [useNTopSubscriptions] = bind(
|
||||
defer(() => of(++nTopSubscriptions)),
|
||||
1,
|
||||
)
|
||||
|
||||
const { result, rerender, unmount } = renderHook(() =>
|
||||
useNTopSubscriptions(),
|
||||
)
|
||||
|
||||
expect(result.current).toBe(1)
|
||||
|
||||
act(() => {
|
||||
rerender()
|
||||
})
|
||||
|
||||
expect(result.current).toBe(1)
|
||||
expect(nTopSubscriptions).toBe(1)
|
||||
|
||||
unmount()
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,6 +2,7 @@ import { Observable } from "rxjs"
|
||||
import shareLatest from "../internal/share-latest"
|
||||
import reactEnhancer from "../internal/react-enhancer"
|
||||
import { useObservable } from "../internal/useObservable"
|
||||
import { EMPTY_VALUE } from "../internal/empty-value"
|
||||
|
||||
/**
|
||||
* Accepts: An Observable.
|
||||
@ -19,9 +20,12 @@ import { useObservable } from "../internal/useObservable"
|
||||
* for the first value.
|
||||
*/
|
||||
const emptyArr: Array<any> = []
|
||||
export default function connectObservable<T>(observable: Observable<T>) {
|
||||
export default function connectObservable<T>(
|
||||
observable: Observable<T>,
|
||||
defaultValue: T = EMPTY_VALUE,
|
||||
) {
|
||||
const sharedObservable$ = shareLatest<T>(observable, false)
|
||||
const getValue = reactEnhancer(sharedObservable$)
|
||||
const getValue = reactEnhancer(sharedObservable$, defaultValue)
|
||||
const useStaticObservable = () =>
|
||||
useObservable(sharedObservable$, getValue, emptyArr)
|
||||
return [useStaticObservable, sharedObservable$] as const
|
||||
|
||||
@ -19,6 +19,7 @@ import connectObservable from "./connectObservable"
|
||||
*/
|
||||
export function bind<T>(
|
||||
observable: Observable<T>,
|
||||
defaultValue?: T,
|
||||
): [() => Exclude<T, typeof SUSPENSE>, Observable<T>]
|
||||
|
||||
/**
|
||||
@ -41,12 +42,11 @@ export function bind<T>(
|
||||
*/
|
||||
export function bind<A extends unknown[], O>(
|
||||
getObservable: (...args: A) => Observable<O>,
|
||||
defaultValue?: O,
|
||||
): [(...args: A) => Exclude<O, typeof SUSPENSE>, (...args: A) => Observable<O>]
|
||||
|
||||
export function bind<A extends unknown[], O>(
|
||||
obs: ((...args: A) => Observable<O>) | Observable<O>,
|
||||
) {
|
||||
return (typeof obs === "function"
|
||||
export function bind(...args: any[]) {
|
||||
return (typeof args[0] === "function"
|
||||
? (connectFactoryObservable as any)
|
||||
: connectObservable)(obs)
|
||||
: connectObservable)(...args)
|
||||
}
|
||||
|
||||
@ -2,15 +2,19 @@ import { SUSPENSE } from "../SUSPENSE"
|
||||
import { BehaviorObservable } from "./BehaviorObservable"
|
||||
import { EMPTY_VALUE } from "./empty-value"
|
||||
|
||||
const reactEnhancer = <T>(source$: BehaviorObservable<T>): (() => T) => {
|
||||
const reactEnhancer = <T>(
|
||||
source$: BehaviorObservable<T>,
|
||||
defaultValue: T,
|
||||
): (() => T) => {
|
||||
let promise: Promise<T | void> | null
|
||||
let error: any = EMPTY_VALUE
|
||||
|
||||
return (): T => {
|
||||
const res = (): T => {
|
||||
const currentValue = source$.getValue()
|
||||
if (currentValue !== SUSPENSE && currentValue !== EMPTY_VALUE) {
|
||||
return currentValue
|
||||
}
|
||||
if (defaultValue !== EMPTY_VALUE) return defaultValue
|
||||
|
||||
let timeoutToken
|
||||
if (error !== EMPTY_VALUE) {
|
||||
@ -56,6 +60,8 @@ const reactEnhancer = <T>(source$: BehaviorObservable<T>): (() => T) => {
|
||||
|
||||
throw error !== EMPTY_VALUE ? error : promise
|
||||
}
|
||||
res.d = defaultValue
|
||||
return res
|
||||
}
|
||||
|
||||
export default reactEnhancer
|
||||
|
||||
@ -14,6 +14,7 @@ export const useObservable = <O>(
|
||||
useEffect(() => {
|
||||
let err: any = EMPTY_VALUE
|
||||
let syncVal: O | typeof SUSPENSE = EMPTY_VALUE
|
||||
|
||||
const onError = (error: any) => {
|
||||
err = error
|
||||
setState(() => {
|
||||
@ -26,13 +27,16 @@ export const useObservable = <O>(
|
||||
}, onError)
|
||||
if (err !== EMPTY_VALUE) return
|
||||
|
||||
const set = (val: O | (() => O)) => {
|
||||
if (!Object.is(val, prevStateRef.current)) {
|
||||
setState((prevStateRef.current = val))
|
||||
}
|
||||
const set = (value: O | (() => O)) => {
|
||||
if (!Object.is(prevStateRef.current, value))
|
||||
setState((prevStateRef.current = value))
|
||||
}
|
||||
|
||||
const defaultValue = (getValue as any).d
|
||||
if (syncVal === EMPTY_VALUE) {
|
||||
set(defaultValue === EMPTY_VALUE ? getValue : defaultValue)
|
||||
}
|
||||
|
||||
if (syncVal === EMPTY_VALUE) set(getValue)
|
||||
const t = subscription
|
||||
subscription = source$.subscribe((value: O | typeof SUSPENSE) => {
|
||||
set(value === SUSPENSE ? getValue : value)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user