From 2d58fbab32bec8422121b2151e4d3496bb5edc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josep=20M=20Sobrepere=20Profit=C3=B3s?= Date: Sun, 16 Aug 2020 01:43:27 +0200 Subject: [PATCH] feat(bind): nested keys accept optional args --- .../bind/connectFactoryObservable.test.tsx | 7 + .../core/src/bind/connectFactoryObservable.ts | 134 +++++++++--------- packages/core/src/bind/index.ts | 10 +- 3 files changed, 77 insertions(+), 74 deletions(-) diff --git a/packages/core/src/bind/connectFactoryObservable.test.tsx b/packages/core/src/bind/connectFactoryObservable.test.tsx index 0a468b8..5f8d65f 100644 --- a/packages/core/src/bind/connectFactoryObservable.test.tsx +++ b/packages/core/src/bind/connectFactoryObservable.test.tsx @@ -127,6 +127,13 @@ describe("connectFactoryObservable", () => { expect(result.current).toBe(2) }) + it("handles optional args correctly", () => { + const [, getNumber$] = bind((x: number, y?: number) => of(x + (y ?? 0))) + + expect(getNumber$(5)).toBe(getNumber$(5, undefined)) + expect(getNumber$(6, undefined)).toBe(getNumber$(6)) + }) + it("suspends the component when the factory-observable hasn't emitted yet.", async () => { const [useDelayedNumber] = bind((x: number) => of(x).pipe(delay(50))) const Result: React.FC<{ input: number }> = (p) => ( diff --git a/packages/core/src/bind/connectFactoryObservable.ts b/packages/core/src/bind/connectFactoryObservable.ts index 945ff34..c031751 100644 --- a/packages/core/src/bind/connectFactoryObservable.ts +++ b/packages/core/src/bind/connectFactoryObservable.ts @@ -6,6 +6,74 @@ import { useObservable } from "../internal/useObservable" import { SUSPENSE } from "../SUSPENSE" import { takeUntilComplete } from "../internal/take-until-complete" +/** + * Accepts: A factory function that returns an Observable. + * + * Returns [1, 2] + * 1. A React Hook function with the same parameters as the factory function. + * This hook will yield the latest update from the observable returned from + * the factory function. + * 2. A `sharedLatest` version of the observable generated by the factory + * function that can be used for composing other streams that depend on it. + * The shared subscription is closed as soon as there are no subscribers to + * that observable. + * + * @param getObservable Factory of observables. The arguments of this function + * will be the ones used in the hook. + * @param unsubscribeGraceTime (= 200): Amount of time in ms that the shared + * observable should wait before unsubscribing from the source observable when + * there are no new subscribers. + * + * @remarks If the Observable doesn't synchronously emit a value upon the first + * subscription, then the hook will leverage React Suspense while it's waiting + * for the first value. + */ +export default function connectFactoryObservable( + getObservable: (...args: A) => Observable, + unsubscribeGraceTime: number, +): [ + (...args: A) => Exclude, + (...args: A) => Observable, +] { + const cache = new NestedMap, BehaviorObservable]>() + + const getSharedObservables$ = ( + input: A, + ): [Observable, BehaviorObservable] => { + for (let i = input.length - 1; input[i] === undefined && i > -1; i--) { + input.splice(-1) + } + const keys = ([input.length, ...input] as any) as A + const cachedVal = cache.get(keys) + + if (cachedVal !== undefined) { + return cachedVal + } + + const sharedObservable$ = shareLatest(getObservable(...input), () => { + cache.delete(keys) + }) + + const reactObservable$ = reactEnhancer( + sharedObservable$, + unsubscribeGraceTime, + ) + + const result: [Observable, BehaviorObservable] = [ + takeUntilComplete(sharedObservable$), + reactObservable$, + ] + + cache.set(keys, result) + return result + } + + return [ + (...input: A) => useObservable(getSharedObservables$(input)[1]), + (...input: A) => getSharedObservables$(input)[0], + ] +} + class NestedMap { private root: Map constructor() { @@ -51,69 +119,3 @@ class NestedMap { } } } - -const emptyInput = [0] -/** - * Accepts: A factory function that returns an Observable. - * - * Returns [1, 2] - * 1. A React Hook function with the same parameters as the factory function. - * This hook will yield the latest update from the observable returned from - * the factory function. - * 2. A `sharedLatest` version of the observable generated by the factory - * function that can be used for composing other streams that depend on it. - * The shared subscription is closed as soon as there are no subscribers to - * that observable. - * - * @param getObservable Factory of observables. The arguments of this function - * will be the ones used in the hook. - * @param unsubscribeGraceTime (= 200): Amount of time in ms that the shared - * observable should wait before unsubscribing from the source observable when - * there are no new subscribers. - * - * @remarks If the Observable doesn't synchronously emit a value upon the first - * subscription, then the hook will leverage React Suspense while it's waiting - * for the first value. - */ -export default function connectFactoryObservable( - getObservable: (...args: A) => Observable, - unsubscribeGraceTime: number, -): [ - (...args: A) => Exclude, - (...args: A) => Observable, -] { - const cache = new NestedMap, BehaviorObservable]>() - - const getSharedObservables$ = ( - input: A, - ): [Observable, BehaviorObservable] => { - const keys = input.length > 0 ? input : (emptyInput as A) - const cachedVal = cache.get(keys) - - if (cachedVal !== undefined) { - return cachedVal - } - - const sharedObservable$ = shareLatest(getObservable(...input), () => { - cache.delete(keys) - }) - - const reactObservable$ = reactEnhancer( - sharedObservable$, - unsubscribeGraceTime, - ) - - const result: [Observable, BehaviorObservable] = [ - takeUntilComplete(sharedObservable$), - reactObservable$, - ] - - cache.set(keys, result) - return result - } - - return [ - (...input: A) => useObservable(getSharedObservables$(input)[1]), - (...input: A) => getSharedObservables$(input)[0], - ] -} diff --git a/packages/core/src/bind/index.ts b/packages/core/src/bind/index.ts index 3fb9b67..e2901d9 100644 --- a/packages/core/src/bind/index.ts +++ b/packages/core/src/bind/index.ts @@ -46,18 +46,12 @@ export function bind( * subscription, then the hook will leverage React Suspense while it's waiting * for the first value. */ -export function bind< - A extends (number | string | boolean | null | Object | Symbol)[], - O ->( +export function bind( getObservable: (...args: A) => Observable, unsubscribeGraceTime?: number, ): [(...args: A) => Exclude, (...args: A) => Observable] -export function bind< - A extends (number | string | boolean | null | Object | Symbol)[], - O ->( +export function bind( obs: ((...args: A) => Observable) | Observable, unsubscribeGraceTime = 200, ) {