From 81ce35d0de6eccd0f3beedacfa8d8fad4e945ebc Mon Sep 17 00:00:00 2001 From: Josep M Sobrepere Date: Thu, 28 May 2020 17:36:32 +0200 Subject: [PATCH] v0.2.0-alpha.0 --- package.json | 3 +- src/connectFactoryObservable.ts | 64 ++++++---------------------- src/connectGroupedObservable.ts | 46 ++++++++++++++++++++ src/connectObservable.ts | 35 +++++---------- src/index.tsx | 2 + src/options.ts | 20 +++++++++ src/useSharedReplayableObservable.ts | 43 +++++++++++++++++++ 7 files changed, 137 insertions(+), 76 deletions(-) create mode 100644 src/connectGroupedObservable.ts create mode 100644 src/options.ts create mode 100644 src/useSharedReplayableObservable.ts diff --git a/package.json b/package.json index 28e4576..affbd62 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { - "version": "0.1.4", + "version": "0.2.0-alpha.0", + "sideEffects": false, "repository": { "type": "git", "url": "git+https://github.com/josepot/react-rxjs.git" diff --git a/src/connectFactoryObservable.ts b/src/connectFactoryObservable.ts index a37e697..a738809 100644 --- a/src/connectFactoryObservable.ts +++ b/src/connectFactoryObservable.ts @@ -1,21 +1,7 @@ -import { useEffect, useState } from "react" -import { Observable, of, race } from "rxjs" -import { delay, take, mapTo } from "rxjs/operators" -import { - StaticObservableOptions, - defaultStaticOptions, -} from "./connectObservable" +import { Observable } from "rxjs" import distinctShareReplay from "./operators/distinct-share-replay" -import reactOptimizations from "./operators/react-optimizations" - -interface FactoryObservableOptions extends StaticObservableOptions { - suspenseTime: number -} - -const defaultOptions: FactoryObservableOptions = { - ...defaultStaticOptions, - suspenseTime: 200, -} +import { FactoryObservableOptions, defaultFactoryOptions } from "./options" +import useSharedReplayableObservable from "./useSharedReplayableObservable" export function connectFactoryObservable< I, @@ -24,14 +10,13 @@ export function connectFactoryObservable< >( getObservable: (...args: A) => Observable, initialValue: I, - options?: Partial>, + _options?: FactoryObservableOptions, ): [(...args: A) => O | I, (...args: A) => Observable] { - const { suspenseTime, unsubscribeGraceTime, compare } = { - ...defaultOptions, - ...options, + const options = { + ...defaultFactoryOptions, + ..._options, } - const reactEnhander = reactOptimizations(unsubscribeGraceTime) const cache = new Map>() const getSharedObservable$ = (...input: A): Observable => { @@ -43,7 +28,7 @@ export function connectFactoryObservable< } const reactObservable$ = getObservable(...input).pipe( - distinctShareReplay(compare, () => { + distinctShareReplay(options.compare, () => { cache.delete(key) }), ) @@ -53,34 +38,13 @@ export function connectFactoryObservable< } return [ - (...input: A) => { - const [value, setValue] = useState(initialValue) + (...input: A) => + useSharedReplayableObservable( + getSharedObservable$(...input), + initialValue, + options, + ), - useEffect(() => { - const sharedObservable$ = getSharedObservable$(...input) - const subscription = reactEnhander(sharedObservable$).subscribe( - setValue, - ) - - if (suspenseTime === 0) { - setValue(initialValue) - } else if (suspenseTime < Infinity) { - subscription.add( - race( - of(initialValue).pipe(delay(suspenseTime)), - sharedObservable$.pipe( - take(1), - mapTo((x: I | O) => x), - ), - ).subscribe(setValue), - ) - } - - return () => subscription.unsubscribe() - }, input) - - return value - }, getSharedObservable$, ] } diff --git a/src/connectGroupedObservable.ts b/src/connectGroupedObservable.ts new file mode 100644 index 0000000..76d1eb2 --- /dev/null +++ b/src/connectGroupedObservable.ts @@ -0,0 +1,46 @@ +import { Observable, GroupedObservable } from "rxjs" +import { map, filter, take, mergeMap } from "rxjs/operators" +import distinctShareReplay from "./operators/distinct-share-replay" +import { FactoryObservableOptions, defaultFactoryOptions } from "./options" +import useSharedReplayableObservable from "./useSharedReplayableObservable" + +const connectGroupedObservable = ( + source$: Observable>, + initialValue: I, + _options?: FactoryObservableOptions, +): [(key: K) => I | O, (key: K) => Observable] => { + const options = { + ...defaultFactoryOptions, + ..._options, + } + const observables = new Map>() + const activeObservables$ = source$.pipe( + map(x => { + observables.set(x.key, x) + return observables + }), + distinctShareReplay( + () => false, + () => observables.clear(), + ), + ) + + const getObservableByKey = (key: K) => + activeObservables$.pipe( + filter(x => x.has(key)), + take(1), + mergeMap(x => x.get(key)!), + distinctShareReplay(options.compare, () => observables.delete(key)), + ) + + const hook = (key: K) => + useSharedReplayableObservable( + getObservableByKey(key), + initialValue, + options, + ) + + return [hook, getObservableByKey] +} + +export default connectGroupedObservable diff --git a/src/connectObservable.ts b/src/connectObservable.ts index 15a41f4..9b89390 100644 --- a/src/connectObservable.ts +++ b/src/connectObservable.ts @@ -1,38 +1,23 @@ -import { useEffect, useState } from "react" import { Observable } from "rxjs" -import reactOptimizations from "./operators/react-optimizations" import distinctShareReplay from "./operators/distinct-share-replay" - -export interface StaticObservableOptions { - unsubscribeGraceTime: number - compare: (a: T, b: T) => boolean -} -export const defaultStaticOptions: StaticObservableOptions = { - unsubscribeGraceTime: 120, - compare: (a, b) => a === b, -} +import { StaticObservableOptions, defaultStaticOptions } from "./options" +import useSharedReplayableObservable from "./useSharedReplayableObservable" export function connectObservable( observable: Observable, initialValue: IO, - options?: Partial>, + _options?: StaticObservableOptions, ) { - const { unsubscribeGraceTime, compare } = { + const options = { ...defaultStaticOptions, - ...options, + ..._options, } - const sharedObservable$ = observable.pipe(distinctShareReplay(compare)) - const reactObservable$ = sharedObservable$.pipe( - reactOptimizations(unsubscribeGraceTime), + const sharedObservable$ = observable.pipe( + distinctShareReplay(options.compare), ) - const useStaticObservable = () => { - const [value, setValue] = useState(initialValue) - useEffect(() => { - const subscription = reactObservable$.subscribe(setValue) - return () => subscription.unsubscribe() - }, []) - return value - } + const useStaticObservable = () => + useSharedReplayableObservable(sharedObservable$, initialValue, options) + return [useStaticObservable, sharedObservable$] as const } diff --git a/src/index.tsx b/src/index.tsx index ff8532b..c308584 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,5 @@ export { connectObservable } from "./connectObservable" export { connectFactoryObservable } from "./connectFactoryObservable" +export { default as connectGroupedObservable } from "./connectGroupedObservable" export { default as distinctShareReplay } from "./operators/distinct-share-replay" +export { default as useSharedReplayableObservable } from "./useSharedReplayableObservable" diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..85af988 --- /dev/null +++ b/src/options.ts @@ -0,0 +1,20 @@ +export interface StaticObservableOptions { + unsubscribeGraceTime?: number + compare?: (a: T, b: T) => boolean +} +export const defaultStaticOptions = { + unsubscribeGraceTime: 120, + compare: (a: any, b: any) => a === b, +} + +export interface FactoryObservableOptions + extends StaticObservableOptions { + suspenseTime?: number +} + +export const defaultFactoryOptions = { + ...defaultStaticOptions, + suspenseTime: 200, +} + +export type ObservableOptions = Omit, "compare"> diff --git a/src/useSharedReplayableObservable.ts b/src/useSharedReplayableObservable.ts new file mode 100644 index 0000000..77e920b --- /dev/null +++ b/src/useSharedReplayableObservable.ts @@ -0,0 +1,43 @@ +import { useState, useEffect } from "react" +import { Observable, of, race } from "rxjs" +import { take, mapTo, delay } from "rxjs/operators" +import reactOptimizations from "./operators/react-optimizations" +import { defaultFactoryOptions, ObservableOptions } from "./options" + +const useSharedReplayableObservable = ( + sharedReplayableObservable$: Observable, + initialValue: I, + options?: ObservableOptions, +) => { + const [value, setValue] = useState(initialValue) + + const { suspenseTime, unsubscribeGraceTime } = { + ...defaultFactoryOptions, + ...options, + } + + useEffect(() => { + const subscription = reactOptimizations(unsubscribeGraceTime)( + sharedReplayableObservable$, + ).subscribe(setValue) + + if (suspenseTime === 0) { + setValue(initialValue) + } else if (suspenseTime < Infinity) { + subscription.add( + race( + of(initialValue).pipe(delay(suspenseTime)), + sharedReplayableObservable$.pipe( + take(1), + mapTo((x: I | O) => x), + ), + ).subscribe(setValue), + ) + } + + return () => subscription.unsubscribe() + }, [sharedReplayableObservable$, suspenseTime, unsubscribeGraceTime]) + return value +} + +export default useSharedReplayableObservable