react-rx 1.0.0-beta.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/codeStyles/Project.xml +0 -6
- package/.idea/inspectionProfiles/Project_Default.xml +0 -4
- package/.idea/workspace.xml +62 -49
- package/dist/cjs/WithObservable.d.ts +3 -3
- package/dist/cjs/WithObservable.js +3 -3
- package/dist/cjs/index.d.ts +6 -2
- package/dist/cjs/index.js +18 -11
- package/dist/cjs/reactiveComponent.d.ts +6 -6
- package/dist/cjs/reactiveComponent.js +8 -26
- package/dist/cjs/useAsObservable.d.ts +9 -0
- package/dist/cjs/useAsObservable.js +31 -0
- package/dist/cjs/useIsomorphicEffect.d.ts +2 -0
- package/dist/cjs/useIsomorphicEffect.js +5 -0
- package/dist/cjs/useObservable.d.ts +6 -15
- package/dist/cjs/useObservable.js +17 -73
- package/dist/cjs/useObservableCallback.d.ts +3 -0
- package/dist/cjs/useObservableCallback.js +24 -0
- package/dist/cjs/useWithObservable.d.ts +9 -0
- package/dist/cjs/useWithObservable.js +11 -0
- package/dist/cjs/utils.d.ts +7 -3
- package/dist/cjs/utils.js +29 -5
- package/dist/cjs/withPropsStream.js +3 -3
- package/dist/es2015/WithObservable.d.ts +3 -3
- package/dist/es2015/index.d.ts +6 -2
- package/dist/es2015/index.js +7 -2
- package/dist/es2015/reactiveComponent.d.ts +6 -6
- package/dist/es2015/reactiveComponent.js +9 -6
- package/dist/es2015/useAsObservable.d.ts +9 -0
- package/dist/es2015/useAsObservable.js +27 -0
- package/dist/es2015/useIsomorphicEffect.d.ts +2 -0
- package/dist/es2015/useIsomorphicEffect.js +2 -0
- package/dist/es2015/useObservable.d.ts +6 -15
- package/dist/es2015/useObservable.js +15 -46
- package/dist/es2015/useObservableCallback.d.ts +3 -0
- package/dist/es2015/useObservableCallback.js +19 -0
- package/dist/es2015/useWithObservable.d.ts +9 -0
- package/dist/es2015/useWithObservable.js +7 -0
- package/dist/es2015/utils.d.ts +7 -3
- package/dist/es2015/utils.js +24 -3
- package/dist/esm/WithObservable.d.ts +3 -3
- package/dist/esm/index.d.ts +6 -2
- package/dist/esm/index.js +7 -2
- package/dist/esm/reactiveComponent.d.ts +6 -6
- package/dist/esm/reactiveComponent.js +7 -6
- package/dist/esm/useAsObservable.d.ts +9 -0
- package/dist/esm/useAsObservable.js +27 -0
- package/dist/esm/useIsomorphicEffect.d.ts +2 -0
- package/dist/esm/useIsomorphicEffect.js +2 -0
- package/dist/esm/useObservable.d.ts +6 -15
- package/dist/esm/useObservable.js +15 -48
- package/dist/esm/useObservableCallback.d.ts +3 -0
- package/dist/esm/useObservableCallback.js +20 -0
- package/dist/esm/useWithObservable.d.ts +9 -0
- package/dist/esm/useWithObservable.js +7 -0
- package/dist/esm/utils.d.ts +7 -3
- package/dist/esm/utils.js +24 -3
- package/package.json +9 -4
- package/src/WithObservable.tsx +4 -3
- package/src/index.ts +12 -12
- package/src/reactiveComponent.ts +28 -20
- package/src/useAsObservable.ts +43 -0
- package/src/useIsomorphicEffect.ts +3 -0
- package/src/useObservable.ts +36 -77
- package/src/useObservableCallback.ts +30 -0
- package/src/useWithObservable.ts +23 -0
- package/src/utils.ts +39 -3
- package/website/.babelrc +21 -0
- package/website/.prettierrc +7 -0
- package/website/README.mdx +1 -0
- package/website/package.json +84 -0
- package/website/src/@types/babel.d.ts +4 -0
- package/website/src/@types/mdx.d.ts +11 -0
- package/website/src/@types/react-css-burger.d.ts +4 -0
- package/website/src/App.tsx +68 -0
- package/website/src/components/Header.tsx +118 -0
- package/website/src/components/Link.tsx +18 -0
- package/website/src/components/LoadPage.tsx +60 -0
- package/website/src/components/Sidebar.tsx +88 -0
- package/website/src/components/logos/Github.tsx +10 -0
- package/website/src/components/logos/React.tsx +27 -0
- package/website/src/components/logos/ReactRxLogo.tsx +39 -0
- package/website/src/components/logos/RxJs.tsx +69 -0
- package/website/src/components/repl/Checkerboard.tsx +25 -0
- package/website/src/components/repl/CodeBlock.tsx +147 -0
- package/website/src/components/repl/CodeMirror.tsx +37 -0
- package/website/src/components/repl/CodeMirrorMode.tsx +67 -0
- package/website/src/components/repl/EvalCode.tsx +55 -0
- package/website/src/components/repl/Prelude.tsx +50 -0
- package/website/src/components/repl/ShowError.tsx +27 -0
- package/website/src/components/repl/codemirror-lib.ts +8 -0
- package/website/src/components/repl/codemirror-theme.css +131 -0
- package/website/src/components/repl/compile/babel.ts +21 -0
- package/website/src/components/repl/eval/evalCallback.ts +27 -0
- package/website/src/components/repl/eval/evalCode.ts +7 -0
- package/website/src/components/repl/eval/evalExpression.ts +4 -0
- package/website/src/components/repl/eval/evalReactDomRender.ts +23 -0
- package/website/src/components/repl/index.ts +1 -0
- package/website/src/components/repl/runMode.ts +42 -0
- package/website/src/datastores/location.ts +14 -0
- package/website/src/datastores/page.ts +11 -0
- package/website/src/datastores/pageTransition.ts +97 -0
- package/website/src/entries/main.css +2 -0
- package/website/src/entries/main.tsx +6 -0
- package/website/src/examples/FizzBuzz/FizzBuzzExample.tsx +39 -0
- package/website/src/examples/FizzBuzz/index.tsx +9 -0
- package/website/src/examples/_utils/globalScope.ts +45 -0
- package/website/src/examples/animation/AnimationExample.tsx +152 -0
- package/website/src/examples/animation/easings.ts +0 -0
- package/website/src/examples/animation/index.tsx +15 -0
- package/website/src/examples/context/Context.example.jsx +67 -0
- package/website/src/examples/context/index.tsx +9 -0
- package/website/src/examples/errors/Errors.example.jsx +69 -0
- package/website/src/examples/errors/index.tsx +9 -0
- package/website/src/examples/event-handlers/EventHandlersExample.tsx +48 -0
- package/website/src/examples/event-handlers/index.tsx +9 -0
- package/website/src/examples/fetch/FetchExample.jsx +43 -0
- package/website/src/examples/fetch/a.txt +1 -0
- package/website/src/examples/fetch/b.txt +1 -0
- package/website/src/examples/fetch/index.tsx +9 -0
- package/website/src/examples/form-data/FormDataExample.jsx +119 -0
- package/website/src/examples/form-data/index.tsx +12 -0
- package/website/src/examples/form-data/storage.ts +27 -0
- package/website/src/examples/forward-ref/ForwardRefExample.tsx +55 -0
- package/website/src/examples/forward-ref/index.tsx +9 -0
- package/website/src/examples/hello-world/HelloWorld.example.jsx +6 -0
- package/website/src/examples/hello-world/index.tsx +9 -0
- package/website/src/examples/hooks-interop/HooksInterop.example.tsx +42 -0
- package/website/src/examples/hooks-interop/index.tsx +9 -0
- package/website/src/examples/passthrough-props/PassThroughPropsExample.jsx +57 -0
- package/website/src/examples/passthrough-props/index.tsx +11 -0
- package/website/src/examples/reactive-state/ReactiveState.example.jsx +43 -0
- package/website/src/examples/reactive-state/index.ts +9 -0
- package/website/src/examples/readme-examples/README.mdx +0 -0
- package/website/src/examples/readme-examples/index.ts +11 -0
- package/website/src/examples/shared-state/SharedState.example.tsx +59 -0
- package/website/src/examples/shared-state/index.tsx +9 -0
- package/website/src/examples/simple/Counter.example.tsx +14 -0
- package/website/src/examples/simple/index.tsx +9 -0
- package/website/src/examples/sync/Sync.example.tsx +9 -0
- package/website/src/examples/sync/index.tsx +9 -0
- package/website/src/examples/tick/TickExample.jsx +51 -0
- package/website/src/examples/tick/index.ts +9 -0
- package/website/src/examples/todo-app/TodoApp.example.tsx +88 -0
- package/website/src/examples/todo-app/index.tsx +12 -0
- package/website/src/examples/todo-app/storage.ts +27 -0
- package/website/src/examples/use-element/UseElementExample.tsx +70 -0
- package/website/src/examples/use-element/index.tsx +9 -0
- package/website/src/examples/use-observable/search/SearchExample.tsx +82 -0
- package/website/src/examples/use-observable/search/index.tsx +8 -0
- package/website/src/examples/use-observable/use-observable/UseObservableExample.tsx +70 -0
- package/website/src/examples/use-observable/use-observable/index.tsx +9 -0
- package/website/src/index.html +10 -0
- package/website/src/mdx-components.tsx +74 -0
- package/website/src/pages/Api/Api.mdx +134 -0
- package/website/src/pages/Api/ApiPage.tsx +25 -0
- package/website/src/pages/Examples/Examples.tsx +105 -0
- package/website/src/pages/Examples/ExamplesPage.tsx +13 -0
- package/website/src/pages/Guide/Guide.mdx +275 -0
- package/website/src/pages/Guide/GuidePage.tsx +25 -0
- package/website/src/pages/Index/IndexPage.tsx +50 -0
- package/website/src/pages/Index/ReactRxReadme.md +20 -0
- package/website/src/pages/NotFound.tsx +3 -0
- package/website/src/pages/pages.ts +46 -0
- package/website/src/pages/styles.tsx +24 -0
- package/website/src/theme.ts +61 -0
- package/website/src/utils/parseCodeFenceHeader.ts +32 -0
- package/website/tsconfig.json +30 -0
- package/website/vercel.json +4 -0
- package/website/webpack/config.js +5 -0
- package/website/webpack/createConfig.js +95 -0
- package/website/webpack/mdx-toc-loader.js +38 -0
|
@@ -1,39 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { createEventHandler } from './utils';
|
|
4
|
-
export function toObservable(value, setup) {
|
|
5
|
-
var isInitial = React.useRef(true);
|
|
6
|
-
var subject = React.useRef(new BehaviorSubject(value));
|
|
7
|
-
React.useEffect(function () {
|
|
8
|
-
if (isInitial.current) {
|
|
9
|
-
isInitial.current = false;
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
// emit only on update
|
|
13
|
-
subject.current.next(value);
|
|
14
|
-
}
|
|
15
|
-
}, [value]);
|
|
16
|
-
React.useEffect(function () {
|
|
17
|
-
return function () {
|
|
18
|
-
return subject.current.complete();
|
|
19
|
-
};
|
|
20
|
-
}, []);
|
|
21
|
-
var observable = subject.current.asObservable();
|
|
22
|
-
return setup ? setup(observable) : observable;
|
|
23
|
-
}
|
|
24
|
-
var isCallable = function (val) {
|
|
25
|
-
return typeof val === 'function';
|
|
26
|
-
};
|
|
1
|
+
import { useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useIsomorphicEffect } from './useIsomorphicEffect';
|
|
27
3
|
function getValue(value) {
|
|
28
|
-
return
|
|
4
|
+
return typeof value === 'function' ? value() : value;
|
|
29
5
|
}
|
|
30
|
-
export function useObservable(observable
|
|
31
|
-
var subscription =
|
|
32
|
-
var isInitial =
|
|
33
|
-
var _a =
|
|
6
|
+
export function useObservable(observable, initialValue) {
|
|
7
|
+
var subscription = useRef();
|
|
8
|
+
var isInitial = useRef(true);
|
|
9
|
+
var _a = useState(function () {
|
|
34
10
|
var isSync = true;
|
|
35
11
|
var syncVal = getValue(initialValue);
|
|
36
|
-
subscription.current = observable
|
|
12
|
+
subscription.current = observable.subscribe(function (nextVal) {
|
|
37
13
|
if (isSync) {
|
|
38
14
|
syncVal = nextVal;
|
|
39
15
|
}
|
|
@@ -44,30 +20,21 @@ export function useObservable(observable$, initialValue) {
|
|
|
44
20
|
isSync = false;
|
|
45
21
|
return syncVal;
|
|
46
22
|
}), value = _a[0], setState = _a[1];
|
|
47
|
-
|
|
48
|
-
// when the observable
|
|
23
|
+
useIsomorphicEffect(function () {
|
|
24
|
+
// when the observable changes after initial (possibly sync render)
|
|
49
25
|
if (!isInitial.current) {
|
|
50
|
-
subscription.current = observable
|
|
26
|
+
subscription.current = observable.subscribe(function (nextVal) { return setState(nextVal); });
|
|
51
27
|
}
|
|
52
28
|
isInitial.current = false;
|
|
53
29
|
return function () {
|
|
54
30
|
if (subscription.current) {
|
|
55
31
|
subscription.current.unsubscribe();
|
|
32
|
+
subscription.current = undefined;
|
|
56
33
|
}
|
|
57
34
|
};
|
|
58
|
-
}, [observable
|
|
35
|
+
}, [observable]);
|
|
59
36
|
return value;
|
|
60
37
|
}
|
|
61
|
-
export function
|
|
62
|
-
|
|
63
|
-
return [toObservable(value), update];
|
|
64
|
-
}
|
|
65
|
-
export function useObservableContext(context) {
|
|
66
|
-
return toObservable(React.useContext(context));
|
|
67
|
-
}
|
|
68
|
-
export function useObservableEvent() {
|
|
69
|
-
return React.useMemo(createEventHandler, []);
|
|
70
|
-
}
|
|
71
|
-
export function useObservableElement() {
|
|
72
|
-
return useObservableState(null);
|
|
38
|
+
export function useMemoObservable(observableOrFactory, deps, initialValue) {
|
|
39
|
+
return useObservable(useMemo(function () { return getValue(observableOrFactory); }, deps), initialValue);
|
|
73
40
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { observableCallback } from 'observable-callback';
|
|
3
|
+
var EMPTY_DEPS = [];
|
|
4
|
+
export function useObservableCallback(fn, dependencies) {
|
|
5
|
+
if (dependencies === void 0) { dependencies = EMPTY_DEPS; }
|
|
6
|
+
var callbackRef = useRef();
|
|
7
|
+
if (!callbackRef.current) {
|
|
8
|
+
callbackRef.current = observableCallback();
|
|
9
|
+
}
|
|
10
|
+
var _a = callbackRef.current, calls$ = _a[0], call = _a[1];
|
|
11
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
12
|
+
var callback = useCallback(fn, dependencies);
|
|
13
|
+
useEffect(function () {
|
|
14
|
+
var subscription = calls$.pipe(callback).subscribe();
|
|
15
|
+
return function () {
|
|
16
|
+
subscription.unsubscribe();
|
|
17
|
+
};
|
|
18
|
+
}, [calls$, call, callback]);
|
|
19
|
+
return call;
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
/**
|
|
3
|
+
* React hook to convert any props or state value into an observable
|
|
4
|
+
* Returns an observable representing updates to any React value (props, state or any other calculated value)
|
|
5
|
+
* Note: the returned observable is the same instance throughout the component lifecycle
|
|
6
|
+
* @param value
|
|
7
|
+
*/
|
|
8
|
+
export declare function useWithObservable<T>(value: T): T | undefined;
|
|
9
|
+
export declare function useWithObservable<T, K>(value: T, operator: (input: Observable<T>) => Observable<K>): K | undefined;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useObservable } from './useObservable';
|
|
2
|
+
import { useAsObservable } from './useAsObservable';
|
|
3
|
+
export function useWithObservable(value, operator) {
|
|
4
|
+
return operator
|
|
5
|
+
? useObservable(useAsObservable(value, operator))
|
|
6
|
+
: useObservable(useAsObservable(value));
|
|
7
|
+
}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare
|
|
1
|
+
import { Observable, OperatorFunction } from 'rxjs';
|
|
2
|
+
import { Dispatch, SetStateAction, Context } from 'react';
|
|
3
|
+
export declare function observeState<T>(initial: T | (() => T)): [Observable<T>, Dispatch<SetStateAction<T>>];
|
|
4
|
+
export declare function observeCallback<T>(): [Observable<T>, (arg: T) => void];
|
|
5
|
+
export declare function observeCallback<T, K>(operator: OperatorFunction<T, K>): [Observable<K>, (arg: T) => void];
|
|
6
|
+
export declare function observeContext<T>(context: Context<T>): Observable<T>;
|
|
7
|
+
export declare function observeElement<T>(): [Observable<T | null>, (el: T | null) => void];
|
package/dist/esm/utils.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
+
import { useAsObservable } from './useAsObservable';
|
|
1
2
|
import { observableCallback } from 'observable-callback';
|
|
2
3
|
import { startWith } from 'rxjs/operators';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
4
|
+
import { useContext, useRef, useState } from 'react';
|
|
5
|
+
var createState = function (initialState) { return observableCallback(startWith(initialState)); };
|
|
6
|
+
export function observeState(initial) {
|
|
7
|
+
var _a = useState(initial), value = _a[0], update = _a[1];
|
|
8
|
+
return [useAsObservable(value), update];
|
|
9
|
+
}
|
|
10
|
+
export function observeCallback(operator) {
|
|
11
|
+
var ref = useRef();
|
|
12
|
+
if (!ref.current) {
|
|
13
|
+
ref.current = operator ? observableCallback(operator) : observableCallback();
|
|
14
|
+
}
|
|
15
|
+
return ref.current;
|
|
16
|
+
}
|
|
17
|
+
export function observeContext(context) {
|
|
18
|
+
return useAsObservable(useContext(context));
|
|
19
|
+
}
|
|
20
|
+
export function observeElement() {
|
|
21
|
+
var ref = useRef();
|
|
22
|
+
if (!ref.current) {
|
|
23
|
+
ref.current = createState(null);
|
|
24
|
+
}
|
|
25
|
+
return ref.current;
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-rx",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "React + RxJS = <3",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/cjs/index.d.ts",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"build:esm": "tsc --module es2015 --target es5 --outDir dist/esm",
|
|
13
13
|
"build:cjs": "tsc --module commonjs --target es5 --outDir dist/cjs",
|
|
14
14
|
"clean": "rimraf dist",
|
|
15
|
+
"dev": "cd website && npm run dev",
|
|
15
16
|
"prepublishOnly": "npm run clean && npm run build",
|
|
16
17
|
"postpublish": "npm run clean",
|
|
17
18
|
"watch": "run-p \"build:* -- --watch\"",
|
|
@@ -24,8 +25,8 @@
|
|
|
24
25
|
"observable-callback": "^1.0.1"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
|
-
"react": ">=16.8",
|
|
28
|
-
"rxjs": "^6"
|
|
28
|
+
"react": ">=16.8 | >= 17",
|
|
29
|
+
"rxjs": "^6 | ^7"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@testing-library/dom": "^7.29.6",
|
|
@@ -59,5 +60,9 @@
|
|
|
59
60
|
"bugs": {
|
|
60
61
|
"url": "https://github.com/bjoerge/react-rx/issues"
|
|
61
62
|
},
|
|
62
|
-
"homepage": "https://react-rx.dev"
|
|
63
|
+
"homepage": "https://react-rx.dev",
|
|
64
|
+
"workspaces": [
|
|
65
|
+
"website",
|
|
66
|
+
"."
|
|
67
|
+
]
|
|
63
68
|
}
|
package/src/WithObservable.tsx
CHANGED
|
@@ -2,20 +2,21 @@ import * as React from 'react'
|
|
|
2
2
|
import {Observable} from 'rxjs'
|
|
3
3
|
import {distinctUntilChanged, map, switchMap} from 'rxjs/operators'
|
|
4
4
|
import {reactiveComponent} from './reactiveComponent'
|
|
5
|
+
import {ComponentType, ReactNode} from 'react'
|
|
5
6
|
|
|
6
7
|
interface Props<T> {
|
|
7
8
|
observable: Observable<T>
|
|
8
|
-
children: (value: T) =>
|
|
9
|
+
children: (value: T) => ReactNode
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
type ObservableComponent<T> =
|
|
12
|
+
type ObservableComponent<T> = ComponentType<Props<T>>
|
|
12
13
|
|
|
13
14
|
function createWithObservable<T>(): ObservableComponent<T> {
|
|
14
15
|
return reactiveComponent((props$: Observable<Props<T>>) =>
|
|
15
16
|
props$.pipe(
|
|
16
17
|
distinctUntilChanged((props, prevProps) => props.observable === prevProps.observable),
|
|
17
18
|
switchMap(
|
|
18
|
-
(props): Observable<
|
|
19
|
+
(props): Observable<ReactNode> =>
|
|
19
20
|
props.observable.pipe(map((observableValue: T) => props.children(observableValue))),
|
|
20
21
|
),
|
|
21
22
|
),
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Main package exports
|
|
2
2
|
export {reactiveComponent, reactiveComponent as rxComponent, forwardRef} from './reactiveComponent'
|
|
3
|
-
export {
|
|
4
|
-
|
|
3
|
+
export {observeState, observeCallback, observeContext, observeElement} from './utils'
|
|
5
4
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from './
|
|
5
|
+
observeState as state,
|
|
6
|
+
observeCallback as handler,
|
|
7
|
+
observeContext as context,
|
|
8
|
+
observeElement as elementRef,
|
|
9
|
+
} from './utils'
|
|
10
|
+
export {observableCallback} from 'observable-callback'
|
|
11
|
+
|
|
12
|
+
// hooks
|
|
13
|
+
export {useObservable, useMemoObservable} from './useObservable'
|
|
14
|
+
export {useAsObservable} from './useAsObservable'
|
|
15
|
+
export {useObservableCallback} from './useObservableCallback'
|
package/src/reactiveComponent.ts
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
1
|
import {Observable} from 'rxjs'
|
|
3
2
|
import {wrapDisplayName} from './displayName'
|
|
4
|
-
import {
|
|
3
|
+
import {useAsObservable} from './useAsObservable'
|
|
4
|
+
import {useObservable} from './useObservable'
|
|
5
|
+
import {
|
|
6
|
+
createElement,
|
|
7
|
+
Fragment,
|
|
8
|
+
FunctionComponent,
|
|
9
|
+
ReactNode,
|
|
10
|
+
Ref,
|
|
11
|
+
useRef,
|
|
12
|
+
forwardRef as reactForwardRef,
|
|
13
|
+
} from 'react'
|
|
5
14
|
|
|
6
|
-
type Component<Props> = (input$: Observable<Props>) => Observable<
|
|
15
|
+
type Component<Props> = (input$: Observable<Props>) => Observable<ReactNode>
|
|
7
16
|
|
|
8
|
-
function fromComponent<Props>(component: Component<Props>):
|
|
9
|
-
const wrappedComponent = (props: Props) =>
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
function fromComponent<Props>(component: Component<Props>): FunctionComponent<Props> {
|
|
18
|
+
const wrappedComponent = (props: Props) => {
|
|
19
|
+
return createElement(
|
|
20
|
+
Fragment,
|
|
12
21
|
null,
|
|
13
|
-
useObservable<
|
|
22
|
+
useObservable<ReactNode>(useRef(component(useAsObservable(props))).current),
|
|
14
23
|
)
|
|
24
|
+
}
|
|
15
25
|
wrappedComponent.displayName = wrapDisplayName(component, 'reactiveComponent')
|
|
16
26
|
return wrappedComponent
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
function fromObservable<Props>(input$: Observable<Props>):
|
|
29
|
+
function fromObservable<Props>(input$: Observable<Props>): FunctionComponent<{}> {
|
|
20
30
|
return function ReactiveComponent() {
|
|
21
|
-
return
|
|
31
|
+
return createElement(Fragment, null, useObservable<ReactNode>(input$))
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
export function reactiveComponent<Props>(observable: Observable<Props>):
|
|
26
|
-
export function reactiveComponent<Props>(
|
|
27
|
-
component: Component<Props>,
|
|
28
|
-
): React.FunctionComponent<Props>
|
|
35
|
+
export function reactiveComponent<Props>(observable: Observable<Props>): FunctionComponent<{}>
|
|
36
|
+
export function reactiveComponent<Props>(component: Component<Props>): FunctionComponent<Props>
|
|
29
37
|
export function reactiveComponent<Props>(
|
|
30
38
|
observableOrComponent: Observable<Props> | Component<Props>,
|
|
31
39
|
) {
|
|
@@ -36,15 +44,15 @@ export function reactiveComponent<Props>(
|
|
|
36
44
|
|
|
37
45
|
type ForwardRefComponent<RefType, Props> = (
|
|
38
46
|
input$: Observable<Props>,
|
|
39
|
-
ref:
|
|
40
|
-
) => Observable<
|
|
47
|
+
ref: Ref<RefType>,
|
|
48
|
+
) => Observable<ReactNode>
|
|
41
49
|
|
|
42
50
|
export function forwardRef<RefType, Props = {}>(component: ForwardRefComponent<RefType, Props>) {
|
|
43
|
-
const wrappedComponent =
|
|
44
|
-
return
|
|
45
|
-
|
|
51
|
+
const wrappedComponent = reactForwardRef((props: Props, ref: Ref<RefType>) => {
|
|
52
|
+
return createElement(
|
|
53
|
+
Fragment,
|
|
46
54
|
null,
|
|
47
|
-
useObservable(component(
|
|
55
|
+
useObservable<ReactNode>(useRef(component(useAsObservable(props), ref)).current),
|
|
48
56
|
)
|
|
49
57
|
})
|
|
50
58
|
wrappedComponent.displayName = wrapDisplayName(component, 'reactiveComponent')
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {BehaviorSubject, Observable, Subject} from 'rxjs'
|
|
2
|
+
import {useRef} from 'react'
|
|
3
|
+
import {useIsomorphicEffect} from './useIsomorphicEffect'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React hook to convert any props or state value into an observable
|
|
7
|
+
* Returns an observable representing updates to any React value (props, state or any other calculated value)
|
|
8
|
+
* Note: the returned observable is the same instance throughout the component lifecycle
|
|
9
|
+
* @param value
|
|
10
|
+
*/
|
|
11
|
+
export function useAsObservable<T>(value: T): Observable<T>
|
|
12
|
+
export function useAsObservable<T, K>(
|
|
13
|
+
value: T,
|
|
14
|
+
operator: (input: Observable<T>) => Observable<K>,
|
|
15
|
+
): Observable<K>
|
|
16
|
+
export function useAsObservable<T, K = T>(
|
|
17
|
+
value: T,
|
|
18
|
+
operator?: (input: Observable<T>) => Observable<K>,
|
|
19
|
+
): Observable<T | K> {
|
|
20
|
+
const isInitial = useRef(true)
|
|
21
|
+
const subjectRef = useRef<Subject<T>>(new BehaviorSubject(value))
|
|
22
|
+
const observableRef = useRef<Observable<T | K>>()
|
|
23
|
+
if (!observableRef.current) {
|
|
24
|
+
const observable = subjectRef.current.asObservable()
|
|
25
|
+
observableRef.current = operator ? observable.pipe(operator) : observable
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
useIsomorphicEffect(() => {
|
|
29
|
+
if (isInitial.current) {
|
|
30
|
+
isInitial.current = false
|
|
31
|
+
} else {
|
|
32
|
+
// emit only on update
|
|
33
|
+
subjectRef.current.next(value)
|
|
34
|
+
}
|
|
35
|
+
}, [value])
|
|
36
|
+
useIsomorphicEffect(() => {
|
|
37
|
+
return () => {
|
|
38
|
+
return subjectRef.current.complete()
|
|
39
|
+
}
|
|
40
|
+
}, [])
|
|
41
|
+
|
|
42
|
+
return observableRef.current
|
|
43
|
+
}
|
package/src/useObservable.ts
CHANGED
|
@@ -1,55 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import {Observable, Subscription} from 'rxjs'
|
|
2
|
+
import {DependencyList, useMemo, useRef, useState} from 'react'
|
|
3
|
+
import {useIsomorphicEffect} from './useIsomorphicEffect'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
value: T,
|
|
8
|
-
setup: (props$: Observable<T>) => Observable<K>,
|
|
9
|
-
): Observable<K>
|
|
10
|
-
export function toObservable<T, K>(
|
|
11
|
-
value: T,
|
|
12
|
-
setup?: (props$: Observable<T>) => Observable<K>,
|
|
13
|
-
): Observable<T | K> {
|
|
14
|
-
const isInitial = React.useRef(true)
|
|
15
|
-
const subject = React.useRef<Subject<T>>(new BehaviorSubject(value))
|
|
16
|
-
|
|
17
|
-
React.useEffect(() => {
|
|
18
|
-
if (isInitial.current) {
|
|
19
|
-
isInitial.current = false
|
|
20
|
-
} else {
|
|
21
|
-
// emit only on update
|
|
22
|
-
subject.current.next(value)
|
|
23
|
-
}
|
|
24
|
-
}, [value])
|
|
25
|
-
React.useEffect(() => {
|
|
26
|
-
return () => {
|
|
27
|
-
return subject.current.complete()
|
|
28
|
-
}
|
|
29
|
-
}, [])
|
|
30
|
-
|
|
31
|
-
const observable = subject.current.asObservable()
|
|
32
|
-
return setup ? setup(observable) : observable
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const isCallable = (val: unknown): val is (...args: unknown[]) => unknown =>
|
|
36
|
-
typeof val === 'function'
|
|
37
|
-
|
|
38
|
-
function getValue<T>(value: T): T
|
|
39
|
-
function getValue<T>(value: T | (() => T)): T {
|
|
40
|
-
return isCallable(value) ? value() : value
|
|
5
|
+
function getValue<T>(value: T): T extends () => infer U ? U : T {
|
|
6
|
+
return typeof value === 'function' ? value() : value
|
|
41
7
|
}
|
|
42
8
|
|
|
43
|
-
export function useObservable<T>(observable
|
|
44
|
-
export function useObservable<T>(observable
|
|
45
|
-
export function useObservable<T>(observable
|
|
46
|
-
export function useObservable<T>(observable
|
|
47
|
-
const subscription =
|
|
48
|
-
const isInitial =
|
|
49
|
-
const [value, setState] =
|
|
9
|
+
export function useObservable<T>(observable: Observable<T>): T | undefined
|
|
10
|
+
export function useObservable<T>(observable: Observable<T>, initialValue: T): T
|
|
11
|
+
export function useObservable<T>(observable: Observable<T>, initialValue: () => T): T
|
|
12
|
+
export function useObservable<T>(observable: Observable<T>, initialValue?: T | (() => T)) {
|
|
13
|
+
const subscription = useRef<Subscription>()
|
|
14
|
+
const isInitial = useRef(true)
|
|
15
|
+
const [value, setState] = useState(() => {
|
|
50
16
|
let isSync = true
|
|
51
17
|
let syncVal = getValue(initialValue)
|
|
52
|
-
subscription.current = observable
|
|
18
|
+
subscription.current = observable.subscribe(nextVal => {
|
|
53
19
|
if (isSync) {
|
|
54
20
|
syncVal = nextVal
|
|
55
21
|
} else {
|
|
@@ -60,46 +26,39 @@ export function useObservable<T>(observable$: Observable<T>, initialValue?: T):
|
|
|
60
26
|
return syncVal
|
|
61
27
|
})
|
|
62
28
|
|
|
63
|
-
|
|
64
|
-
// when the observable
|
|
29
|
+
useIsomorphicEffect(() => {
|
|
30
|
+
// when the observable changes after initial (possibly sync render)
|
|
65
31
|
if (!isInitial.current) {
|
|
66
|
-
subscription.current = observable
|
|
32
|
+
subscription.current = observable.subscribe(nextVal => setState(nextVal))
|
|
67
33
|
}
|
|
68
34
|
isInitial.current = false
|
|
69
35
|
return () => {
|
|
70
36
|
if (subscription.current) {
|
|
71
37
|
subscription.current.unsubscribe()
|
|
38
|
+
subscription.current = undefined
|
|
72
39
|
}
|
|
73
40
|
}
|
|
74
|
-
}, [observable
|
|
41
|
+
}, [observable])
|
|
75
42
|
|
|
76
43
|
return value
|
|
77
44
|
}
|
|
78
45
|
|
|
79
|
-
export function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return React.useMemo<ObservableEventTuple<Event>>(createEventHandler, [])
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function useObservableElement<Element extends HTMLElement>(): [
|
|
101
|
-
Observable<Element | null>,
|
|
102
|
-
(el: Element | null) => void,
|
|
103
|
-
] {
|
|
104
|
-
return useObservableState<Element | null>(null)
|
|
46
|
+
export function useMemoObservable<T>(
|
|
47
|
+
observableOrFactory: Observable<T> | (() => Observable<T>),
|
|
48
|
+
deps: DependencyList,
|
|
49
|
+
): T | undefined
|
|
50
|
+
export function useMemoObservable<T>(
|
|
51
|
+
observableOrFactory: Observable<T> | (() => Observable<T>),
|
|
52
|
+
deps: DependencyList,
|
|
53
|
+
initialValue: T | (() => T),
|
|
54
|
+
): T
|
|
55
|
+
export function useMemoObservable<T>(
|
|
56
|
+
observableOrFactory: Observable<T> | (() => Observable<T>),
|
|
57
|
+
deps: DependencyList,
|
|
58
|
+
initialValue?: T | (() => T),
|
|
59
|
+
) {
|
|
60
|
+
return useObservable(
|
|
61
|
+
useMemo(() => getValue(observableOrFactory), deps),
|
|
62
|
+
initialValue,
|
|
63
|
+
)
|
|
105
64
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {DependencyList, useCallback, useEffect, useRef} from 'react'
|
|
2
|
+
import {observableCallback} from 'observable-callback'
|
|
3
|
+
import {Observable} from 'rxjs'
|
|
4
|
+
|
|
5
|
+
const EMPTY_DEPS: DependencyList = []
|
|
6
|
+
|
|
7
|
+
export function useObservableCallback<T, U>(
|
|
8
|
+
fn: (arg: Observable<T>) => Observable<U>,
|
|
9
|
+
dependencies: DependencyList = EMPTY_DEPS,
|
|
10
|
+
): (arg: T) => void {
|
|
11
|
+
const callbackRef = useRef<[Observable<T>, (val: T) => void]>()
|
|
12
|
+
|
|
13
|
+
if (!callbackRef.current) {
|
|
14
|
+
callbackRef.current = observableCallback<T>()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [calls$, call] = callbackRef.current
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
20
|
+
const callback = useCallback(fn, dependencies)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const subscription = calls$.pipe(callback).subscribe()
|
|
24
|
+
return () => {
|
|
25
|
+
subscription.unsubscribe()
|
|
26
|
+
}
|
|
27
|
+
}, [calls$, call, callback])
|
|
28
|
+
|
|
29
|
+
return call
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {Observable} from 'rxjs'
|
|
2
|
+
import {useObservable} from './useObservable'
|
|
3
|
+
import {useAsObservable} from './useAsObservable'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React hook to convert any props or state value into an observable
|
|
7
|
+
* Returns an observable representing updates to any React value (props, state or any other calculated value)
|
|
8
|
+
* Note: the returned observable is the same instance throughout the component lifecycle
|
|
9
|
+
* @param value
|
|
10
|
+
*/
|
|
11
|
+
export function useWithObservable<T>(value: T): T | undefined
|
|
12
|
+
export function useWithObservable<T, K>(
|
|
13
|
+
value: T,
|
|
14
|
+
operator: (input: Observable<T>) => Observable<K>,
|
|
15
|
+
): K | undefined
|
|
16
|
+
export function useWithObservable<T, K = T>(
|
|
17
|
+
value: T,
|
|
18
|
+
operator?: (input: Observable<T>) => Observable<K>,
|
|
19
|
+
): T | K | undefined {
|
|
20
|
+
return operator
|
|
21
|
+
? useObservable(useAsObservable(value, operator))
|
|
22
|
+
: useObservable(useAsObservable(value))
|
|
23
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,43 @@
|
|
|
1
|
+
import {Observable, OperatorFunction} from 'rxjs'
|
|
2
|
+
import {useAsObservable} from './useAsObservable'
|
|
1
3
|
import {observableCallback} from 'observable-callback'
|
|
2
4
|
import {startWith} from 'rxjs/operators'
|
|
5
|
+
import {Dispatch, SetStateAction, useContext, useRef, useState, Context} from 'react'
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
export const createEventHandler = observableCallback
|
|
7
|
+
const createState = <T>(initialState: T) => observableCallback(startWith<T, T>(initialState))
|
|
6
8
|
|
|
7
|
-
export
|
|
9
|
+
export function observeState<T>(
|
|
10
|
+
initial: T | (() => T),
|
|
11
|
+
): [Observable<T>, Dispatch<SetStateAction<T>>]
|
|
12
|
+
export function observeState<T>(
|
|
13
|
+
initial?: T | (() => T),
|
|
14
|
+
): [Observable<T | undefined>, Dispatch<SetStateAction<T | undefined>>] {
|
|
15
|
+
const [value, update] = useState<T | undefined>(initial)
|
|
16
|
+
return [useAsObservable(value), update]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function observeCallback<T>(): [Observable<T>, (arg: T) => void]
|
|
20
|
+
export function observeCallback<T, K>(
|
|
21
|
+
operator: OperatorFunction<T, K>,
|
|
22
|
+
): [Observable<K>, (arg: T) => void]
|
|
23
|
+
export function observeCallback<T, K>(
|
|
24
|
+
operator?: OperatorFunction<T, K>,
|
|
25
|
+
): [Observable<T | K>, (arg: T) => void] {
|
|
26
|
+
const ref = useRef<[Observable<T | K>, (arg: T) => void]>()
|
|
27
|
+
if (!ref.current) {
|
|
28
|
+
ref.current = operator ? observableCallback<T, K>(operator) : observableCallback<T>()
|
|
29
|
+
}
|
|
30
|
+
return ref.current
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function observeContext<T>(context: Context<T>): Observable<T> {
|
|
34
|
+
return useAsObservable(useContext<T>(context))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function observeElement<T>(): [Observable<T | null>, (el: T | null) => void] {
|
|
38
|
+
const ref = useRef<[Observable<T | null>, (value: T | null) => void]>()
|
|
39
|
+
if (!ref.current) {
|
|
40
|
+
ref.current = createState<T | null>(null)
|
|
41
|
+
}
|
|
42
|
+
return ref.current
|
|
43
|
+
}
|
package/website/.babelrc
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"presets": [
|
|
3
|
+
"@babel/react",
|
|
4
|
+
"@babel/typescript",
|
|
5
|
+
[
|
|
6
|
+
"@babel/env",
|
|
7
|
+
{
|
|
8
|
+
"targets": {
|
|
9
|
+
"esmodules": true
|
|
10
|
+
},
|
|
11
|
+
"modules": false
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
],
|
|
15
|
+
"plugins": [
|
|
16
|
+
"@babel/plugin-proposal-optional-chaining",
|
|
17
|
+
"@babel/plugin-proposal-class-properties",
|
|
18
|
+
"babel-plugin-static-fs",
|
|
19
|
+
"babel-plugin-styled-components"
|
|
20
|
+
]
|
|
21
|
+
}
|