diff --git a/package-lock.json b/package-lock.json index 5044182..488373e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@josepot/react-rxjs", - "version": "0.2.0-alpha.7", + "version": "0.2.0-alpha.8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1476,9 +1476,9 @@ } }, "@testing-library/react-hooks": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.2.1.tgz", - "integrity": "sha512-1OB6Ksvlk6BCJA1xpj8/WWz0XVd1qRcgqdaFAq+xeC6l61Ucj0P6QpA5u+Db/x9gU4DCX8ziR5b66Mlfg0M2RA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.3.0.tgz", + "integrity": "sha512-rE9geI1+HJ6jqXkzzJ6abREbeud6bLF8OmF+Vyc7gBoPwZAEVBYjbC1up5nNoVfYBhO5HUwdD4u9mTehAUeiyw==", "dev": true, "requires": { "@babel/runtime": "^7.5.4", @@ -9197,9 +9197,9 @@ "dev": true }, "typescript": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", - "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", "dev": true }, "unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 0214ac9..a5d6e88 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.2.0-alpha.7", + "version": "0.2.0-alpha.8", "sideEffects": false, "repository": { "type": "git", @@ -37,7 +37,7 @@ "module": "dist/react-rxjs.esm.js", "devDependencies": { "@josepot/rxjs-utils": "^0.12.0", - "@testing-library/react-hooks": "^3.2.1", + "@testing-library/react-hooks": "^3.3.0", "@types/jest": "^25.2.3", "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", @@ -49,6 +49,6 @@ "rxjs": "^6.5.5", "tsdx": "^0.13.2", "tslib": "^2.0.0", - "typescript": "^3.9.3" + "typescript": "^3.9.5" } } diff --git a/src/connectFactoryObservable.ts b/src/connectFactoryObservable.ts index 84e6b51..f9a89cb 100644 --- a/src/connectFactoryObservable.ts +++ b/src/connectFactoryObservable.ts @@ -1,7 +1,7 @@ import { Observable, NEVER, concat } from "rxjs" import distinctShareReplay from "./operators/distinct-share-replay" import { FactoryObservableOptions, defaultFactoryOptions } from "./options" -import useSharedReplayableObservable from "./useSharedReplayableObservable" +import useObservable from "./useObservable" export function connectFactoryObservable< I, @@ -39,11 +39,7 @@ export function connectFactoryObservable< return [ (...input: A) => - useSharedReplayableObservable( - getSharedObservable$(...input), - initialValue, - options, - ), + useObservable(getSharedObservable$(...input), initialValue, options), getSharedObservable$, ] diff --git a/src/connectObservable.ts b/src/connectObservable.ts index 30b744e..68fd36a 100644 --- a/src/connectObservable.ts +++ b/src/connectObservable.ts @@ -1,7 +1,7 @@ import { Observable, NEVER, concat } from "rxjs" import distinctShareReplay from "./operators/distinct-share-replay" import { StaticObservableOptions, defaultStaticOptions } from "./options" -import useSharedReplayableObservable from "./useSharedReplayableObservable" +import useObservable from "./useObservable" export function connectObservable( observable: Observable, @@ -18,7 +18,7 @@ export function connectObservable( ) const useStaticObservable = () => - useSharedReplayableObservable(sharedObservable$, initialValue, options) + useObservable(sharedObservable$, initialValue, options) return [useStaticObservable, sharedObservable$] as const } diff --git a/src/index.tsx b/src/index.tsx index d7ab2fb..2866235 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,4 +2,4 @@ export { connectObservable } from "./connectObservable" export { connectFactoryObservable } from "./connectFactoryObservable" export { connectInstanceObservable } from "./connectInstanceObservable" export { default as distinctShareReplay } from "./operators/distinct-share-replay" -export { default as useSharedReplayableObservable } from "./useSharedReplayableObservable" +export { default as useObservable } from "./useObservable" diff --git a/src/useObservable.ts b/src/useObservable.ts new file mode 100644 index 0000000..b1e3151 --- /dev/null +++ b/src/useObservable.ts @@ -0,0 +1,41 @@ +import { useState, useLayoutEffect } from "react" +import { Observable } from "rxjs" +import delayUnsubscription from "./operators/delay-unsubscription" +import { defaultFactoryOptions, ObservableOptions } from "./options" + +const useObservable = ( + source$: Observable, + initialValue: I, + options?: ObservableOptions, +) => { + const { suspenseTime, unsubscribeGraceTime } = { + ...defaultFactoryOptions, + ...options, + } + const [state, setState] = useState(initialValue) + + useLayoutEffect(() => { + let timeoutToken = + suspenseTime === Infinity + ? undefined + : setTimeout(setState, suspenseTime, initialValue) + + const stopInitialState = () => { + if (!timeoutToken) return + timeoutToken = clearTimeout(timeoutToken) as undefined + } + + const subscription = delayUnsubscription(unsubscribeGraceTime)( + source$, + ).subscribe(nextState => { + setState(nextState as any) + stopInitialState() + }) + + return () => subscription.unsubscribe() + }, [source$, suspenseTime, unsubscribeGraceTime]) + + return state +} + +export default useObservable diff --git a/src/useSharedReplayableObservable.ts b/src/useSharedReplayableObservable.ts deleted file mode 100644 index 405015a..0000000 --- a/src/useSharedReplayableObservable.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useState, useLayoutEffect } from "react" -import { Observable, of, race, concat } from "rxjs" -import { delay } from "rxjs/operators" -import delayUnsubscription from "./operators/delay-unsubscription" -import { defaultFactoryOptions, ObservableOptions } from "./options" - -const useSharedReplayableObservable = ( - sharedReplayableObservable$: Observable, - initialValue: I, - options?: ObservableOptions, -) => { - const { suspenseTime, unsubscribeGraceTime } = { - ...defaultFactoryOptions, - ...options, - } - const [state, setState] = useState(initialValue) - - useLayoutEffect(() => { - const updates$ = sharedReplayableObservable$.pipe( - delayUnsubscription(unsubscribeGraceTime), - ) - - const subscription = (suspenseTime === Infinity - ? updates$ - : race( - concat(of(initialValue).pipe(delay(suspenseTime)), updates$), - updates$, - ) - ).subscribe(setState) - - return () => subscription.unsubscribe() - }, [sharedReplayableObservable$, suspenseTime, unsubscribeGraceTime]) - - return state -} - -export default useSharedReplayableObservable diff --git a/test/useObservable.test.ts b/test/useObservable.test.ts new file mode 100644 index 0000000..cb1e711 --- /dev/null +++ b/test/useObservable.test.ts @@ -0,0 +1,33 @@ +import { defer, from, of } from "rxjs" +import { useObservable } from "../src" +import { renderHook, act } from "@testing-library/react-hooks" +import { concatMap, delay } from "rxjs/operators" + +const wait = (ms: number) => new Promise(res => setTimeout(res, ms)) + +describe("useObservable", () => { + it("works", async () => { + let counter = 0 + const source$ = defer(() => { + counter++ + return from([1, 2, 3, 4]).pipe(concatMap(x => of(x).pipe(delay(10)))) + }) + + const { result } = renderHook(() => + useObservable(source$, 0, { + suspenseTime: 0, + unsubscribeGraceTime: 0, + }), + ) + + expect(result.current).toEqual(0) + expect(counter).toBe(1) + + await act(async () => { + await wait(50) + }) + + expect(result.current).toEqual(4) + expect(counter).toBe(1) + }) +})