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.
Files changed (171) hide show
  1. package/.idea/codeStyles/Project.xml +0 -6
  2. package/.idea/inspectionProfiles/Project_Default.xml +0 -4
  3. package/.idea/workspace.xml +62 -49
  4. package/dist/cjs/WithObservable.d.ts +3 -3
  5. package/dist/cjs/WithObservable.js +3 -3
  6. package/dist/cjs/index.d.ts +6 -2
  7. package/dist/cjs/index.js +18 -11
  8. package/dist/cjs/reactiveComponent.d.ts +6 -6
  9. package/dist/cjs/reactiveComponent.js +8 -26
  10. package/dist/cjs/useAsObservable.d.ts +9 -0
  11. package/dist/cjs/useAsObservable.js +31 -0
  12. package/dist/cjs/useIsomorphicEffect.d.ts +2 -0
  13. package/dist/cjs/useIsomorphicEffect.js +5 -0
  14. package/dist/cjs/useObservable.d.ts +6 -15
  15. package/dist/cjs/useObservable.js +17 -73
  16. package/dist/cjs/useObservableCallback.d.ts +3 -0
  17. package/dist/cjs/useObservableCallback.js +24 -0
  18. package/dist/cjs/useWithObservable.d.ts +9 -0
  19. package/dist/cjs/useWithObservable.js +11 -0
  20. package/dist/cjs/utils.d.ts +7 -3
  21. package/dist/cjs/utils.js +29 -5
  22. package/dist/cjs/withPropsStream.js +3 -3
  23. package/dist/es2015/WithObservable.d.ts +3 -3
  24. package/dist/es2015/index.d.ts +6 -2
  25. package/dist/es2015/index.js +7 -2
  26. package/dist/es2015/reactiveComponent.d.ts +6 -6
  27. package/dist/es2015/reactiveComponent.js +9 -6
  28. package/dist/es2015/useAsObservable.d.ts +9 -0
  29. package/dist/es2015/useAsObservable.js +27 -0
  30. package/dist/es2015/useIsomorphicEffect.d.ts +2 -0
  31. package/dist/es2015/useIsomorphicEffect.js +2 -0
  32. package/dist/es2015/useObservable.d.ts +6 -15
  33. package/dist/es2015/useObservable.js +15 -46
  34. package/dist/es2015/useObservableCallback.d.ts +3 -0
  35. package/dist/es2015/useObservableCallback.js +19 -0
  36. package/dist/es2015/useWithObservable.d.ts +9 -0
  37. package/dist/es2015/useWithObservable.js +7 -0
  38. package/dist/es2015/utils.d.ts +7 -3
  39. package/dist/es2015/utils.js +24 -3
  40. package/dist/esm/WithObservable.d.ts +3 -3
  41. package/dist/esm/index.d.ts +6 -2
  42. package/dist/esm/index.js +7 -2
  43. package/dist/esm/reactiveComponent.d.ts +6 -6
  44. package/dist/esm/reactiveComponent.js +7 -6
  45. package/dist/esm/useAsObservable.d.ts +9 -0
  46. package/dist/esm/useAsObservable.js +27 -0
  47. package/dist/esm/useIsomorphicEffect.d.ts +2 -0
  48. package/dist/esm/useIsomorphicEffect.js +2 -0
  49. package/dist/esm/useObservable.d.ts +6 -15
  50. package/dist/esm/useObservable.js +15 -48
  51. package/dist/esm/useObservableCallback.d.ts +3 -0
  52. package/dist/esm/useObservableCallback.js +20 -0
  53. package/dist/esm/useWithObservable.d.ts +9 -0
  54. package/dist/esm/useWithObservable.js +7 -0
  55. package/dist/esm/utils.d.ts +7 -3
  56. package/dist/esm/utils.js +24 -3
  57. package/package.json +9 -4
  58. package/src/WithObservable.tsx +4 -3
  59. package/src/index.ts +12 -12
  60. package/src/reactiveComponent.ts +28 -20
  61. package/src/useAsObservable.ts +43 -0
  62. package/src/useIsomorphicEffect.ts +3 -0
  63. package/src/useObservable.ts +36 -77
  64. package/src/useObservableCallback.ts +30 -0
  65. package/src/useWithObservable.ts +23 -0
  66. package/src/utils.ts +39 -3
  67. package/website/.babelrc +21 -0
  68. package/website/.prettierrc +7 -0
  69. package/website/README.mdx +1 -0
  70. package/website/package.json +84 -0
  71. package/website/src/@types/babel.d.ts +4 -0
  72. package/website/src/@types/mdx.d.ts +11 -0
  73. package/website/src/@types/react-css-burger.d.ts +4 -0
  74. package/website/src/App.tsx +68 -0
  75. package/website/src/components/Header.tsx +118 -0
  76. package/website/src/components/Link.tsx +18 -0
  77. package/website/src/components/LoadPage.tsx +60 -0
  78. package/website/src/components/Sidebar.tsx +88 -0
  79. package/website/src/components/logos/Github.tsx +10 -0
  80. package/website/src/components/logos/React.tsx +27 -0
  81. package/website/src/components/logos/ReactRxLogo.tsx +39 -0
  82. package/website/src/components/logos/RxJs.tsx +69 -0
  83. package/website/src/components/repl/Checkerboard.tsx +25 -0
  84. package/website/src/components/repl/CodeBlock.tsx +147 -0
  85. package/website/src/components/repl/CodeMirror.tsx +37 -0
  86. package/website/src/components/repl/CodeMirrorMode.tsx +67 -0
  87. package/website/src/components/repl/EvalCode.tsx +55 -0
  88. package/website/src/components/repl/Prelude.tsx +50 -0
  89. package/website/src/components/repl/ShowError.tsx +27 -0
  90. package/website/src/components/repl/codemirror-lib.ts +8 -0
  91. package/website/src/components/repl/codemirror-theme.css +131 -0
  92. package/website/src/components/repl/compile/babel.ts +21 -0
  93. package/website/src/components/repl/eval/evalCallback.ts +27 -0
  94. package/website/src/components/repl/eval/evalCode.ts +7 -0
  95. package/website/src/components/repl/eval/evalExpression.ts +4 -0
  96. package/website/src/components/repl/eval/evalReactDomRender.ts +23 -0
  97. package/website/src/components/repl/index.ts +1 -0
  98. package/website/src/components/repl/runMode.ts +42 -0
  99. package/website/src/datastores/location.ts +14 -0
  100. package/website/src/datastores/page.ts +11 -0
  101. package/website/src/datastores/pageTransition.ts +97 -0
  102. package/website/src/entries/main.css +2 -0
  103. package/website/src/entries/main.tsx +6 -0
  104. package/website/src/examples/FizzBuzz/FizzBuzzExample.tsx +39 -0
  105. package/website/src/examples/FizzBuzz/index.tsx +9 -0
  106. package/website/src/examples/_utils/globalScope.ts +45 -0
  107. package/website/src/examples/animation/AnimationExample.tsx +152 -0
  108. package/website/src/examples/animation/easings.ts +0 -0
  109. package/website/src/examples/animation/index.tsx +15 -0
  110. package/website/src/examples/context/Context.example.jsx +67 -0
  111. package/website/src/examples/context/index.tsx +9 -0
  112. package/website/src/examples/errors/Errors.example.jsx +69 -0
  113. package/website/src/examples/errors/index.tsx +9 -0
  114. package/website/src/examples/event-handlers/EventHandlersExample.tsx +48 -0
  115. package/website/src/examples/event-handlers/index.tsx +9 -0
  116. package/website/src/examples/fetch/FetchExample.jsx +43 -0
  117. package/website/src/examples/fetch/a.txt +1 -0
  118. package/website/src/examples/fetch/b.txt +1 -0
  119. package/website/src/examples/fetch/index.tsx +9 -0
  120. package/website/src/examples/form-data/FormDataExample.jsx +119 -0
  121. package/website/src/examples/form-data/index.tsx +12 -0
  122. package/website/src/examples/form-data/storage.ts +27 -0
  123. package/website/src/examples/forward-ref/ForwardRefExample.tsx +55 -0
  124. package/website/src/examples/forward-ref/index.tsx +9 -0
  125. package/website/src/examples/hello-world/HelloWorld.example.jsx +6 -0
  126. package/website/src/examples/hello-world/index.tsx +9 -0
  127. package/website/src/examples/hooks-interop/HooksInterop.example.tsx +42 -0
  128. package/website/src/examples/hooks-interop/index.tsx +9 -0
  129. package/website/src/examples/passthrough-props/PassThroughPropsExample.jsx +57 -0
  130. package/website/src/examples/passthrough-props/index.tsx +11 -0
  131. package/website/src/examples/reactive-state/ReactiveState.example.jsx +43 -0
  132. package/website/src/examples/reactive-state/index.ts +9 -0
  133. package/website/src/examples/readme-examples/README.mdx +0 -0
  134. package/website/src/examples/readme-examples/index.ts +11 -0
  135. package/website/src/examples/shared-state/SharedState.example.tsx +59 -0
  136. package/website/src/examples/shared-state/index.tsx +9 -0
  137. package/website/src/examples/simple/Counter.example.tsx +14 -0
  138. package/website/src/examples/simple/index.tsx +9 -0
  139. package/website/src/examples/sync/Sync.example.tsx +9 -0
  140. package/website/src/examples/sync/index.tsx +9 -0
  141. package/website/src/examples/tick/TickExample.jsx +51 -0
  142. package/website/src/examples/tick/index.ts +9 -0
  143. package/website/src/examples/todo-app/TodoApp.example.tsx +88 -0
  144. package/website/src/examples/todo-app/index.tsx +12 -0
  145. package/website/src/examples/todo-app/storage.ts +27 -0
  146. package/website/src/examples/use-element/UseElementExample.tsx +70 -0
  147. package/website/src/examples/use-element/index.tsx +9 -0
  148. package/website/src/examples/use-observable/search/SearchExample.tsx +82 -0
  149. package/website/src/examples/use-observable/search/index.tsx +8 -0
  150. package/website/src/examples/use-observable/use-observable/UseObservableExample.tsx +70 -0
  151. package/website/src/examples/use-observable/use-observable/index.tsx +9 -0
  152. package/website/src/index.html +10 -0
  153. package/website/src/mdx-components.tsx +74 -0
  154. package/website/src/pages/Api/Api.mdx +134 -0
  155. package/website/src/pages/Api/ApiPage.tsx +25 -0
  156. package/website/src/pages/Examples/Examples.tsx +105 -0
  157. package/website/src/pages/Examples/ExamplesPage.tsx +13 -0
  158. package/website/src/pages/Guide/Guide.mdx +275 -0
  159. package/website/src/pages/Guide/GuidePage.tsx +25 -0
  160. package/website/src/pages/Index/IndexPage.tsx +50 -0
  161. package/website/src/pages/Index/ReactRxReadme.md +20 -0
  162. package/website/src/pages/NotFound.tsx +3 -0
  163. package/website/src/pages/pages.ts +46 -0
  164. package/website/src/pages/styles.tsx +24 -0
  165. package/website/src/theme.ts +61 -0
  166. package/website/src/utils/parseCodeFenceHeader.ts +32 -0
  167. package/website/tsconfig.json +30 -0
  168. package/website/vercel.json +4 -0
  169. package/website/webpack/config.js +5 -0
  170. package/website/webpack/createConfig.js +95 -0
  171. package/website/webpack/mdx-toc-loader.js +38 -0
@@ -1,39 +1,15 @@
1
- import * as React from 'react';
2
- import { BehaviorSubject } from 'rxjs';
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 isCallable(value) ? value() : value;
4
+ return typeof value === 'function' ? value() : value;
29
5
  }
30
- export function useObservable(observable$, initialValue) {
31
- var subscription = React.useRef();
32
- var isInitial = React.useRef(true);
33
- var _a = React.useState(function () {
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$.subscribe(function (nextVal) {
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
- React.useEffect(function () {
48
- // when the observable$ changes after initial (possibly sync render)
23
+ useIsomorphicEffect(function () {
24
+ // when the observable changes after initial (possibly sync render)
49
25
  if (!isInitial.current) {
50
- subscription.current = observable$.subscribe(function (nextVal) { return setState(nextVal); });
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 useObservableState(initial) {
62
- var _a = React.useState(initial), value = _a[0], update = _a[1];
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,3 @@
1
+ import { DependencyList } from 'react';
2
+ import { Observable } from 'rxjs';
3
+ export declare function useObservableCallback<T, U>(fn: (arg: Observable<T>) => Observable<U>, dependencies?: DependencyList): (arg: T) => void;
@@ -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
+ }
@@ -1,3 +1,7 @@
1
- import { observableCallback } from 'observable-callback';
2
- export declare const createEventHandler: typeof observableCallback;
3
- export declare const createState: <T>(initialState: T) => [import("rxjs").Observable<T>, (arg: T) => void];
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
- // for consumption outside of react only
4
- export var createEventHandler = observableCallback;
5
- export var createState = function (initialState) { return observableCallback(startWith(initialState)); };
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-beta.3",
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
  }
@@ -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) => React.ReactNode
9
+ children: (value: T) => ReactNode
9
10
  }
10
11
 
11
- type ObservableComponent<T> = React.ComponentType<Props<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<React.ReactNode> =>
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 {createEventHandler, createState} from './utils'
4
-
3
+ export {observeState, observeCallback, observeContext, observeElement} from './utils'
5
4
  export {
6
- useObservableState as useState,
7
- useObservableState,
8
- useObservableContext as useContext,
9
- useObservableContext,
10
- useObservableEvent as useEvent,
11
- useObservableEvent,
12
- useObservableElement as useElement,
13
- useObservable,
14
- toObservable,
15
- } from './useObservable'
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'
@@ -1,31 +1,39 @@
1
- import * as React from 'react'
2
1
  import {Observable} from 'rxjs'
3
2
  import {wrapDisplayName} from './displayName'
4
- import {toObservable, useObservable} from './useObservable'
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<React.ReactNode>
15
+ type Component<Props> = (input$: Observable<Props>) => Observable<ReactNode>
7
16
 
8
- function fromComponent<Props>(component: Component<Props>): React.FunctionComponent<Props> {
9
- const wrappedComponent = (props: Props) =>
10
- React.createElement(
11
- React.Fragment,
17
+ function fromComponent<Props>(component: Component<Props>): FunctionComponent<Props> {
18
+ const wrappedComponent = (props: Props) => {
19
+ return createElement(
20
+ Fragment,
12
21
  null,
13
- useObservable<React.ReactNode>(toObservable(props, component)),
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>): React.FunctionComponent<{}> {
29
+ function fromObservable<Props>(input$: Observable<Props>): FunctionComponent<{}> {
20
30
  return function ReactiveComponent() {
21
- return React.createElement(React.Fragment, null, useObservable<React.ReactNode>(input$))
31
+ return createElement(Fragment, null, useObservable<ReactNode>(input$))
22
32
  }
23
33
  }
24
34
 
25
- export function reactiveComponent<Props>(observable: Observable<Props>): React.FunctionComponent<{}>
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: React.Ref<RefType>,
40
- ) => Observable<React.ReactNode>
47
+ ref: Ref<RefType>,
48
+ ) => Observable<ReactNode>
41
49
 
42
50
  export function forwardRef<RefType, Props = {}>(component: ForwardRefComponent<RefType, Props>) {
43
- const wrappedComponent = React.forwardRef((props: Props, ref: React.Ref<RefType>) => {
44
- return React.createElement(
45
- React.Fragment,
51
+ const wrappedComponent = reactForwardRef((props: Props, ref: Ref<RefType>) => {
52
+ return createElement(
53
+ Fragment,
46
54
  null,
47
- useObservable(component(toObservable(props), ref)),
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
+ }
@@ -0,0 +1,3 @@
1
+ import {useEffect, useLayoutEffect} from 'react'
2
+
3
+ export const useIsomorphicEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
@@ -1,55 +1,21 @@
1
- import * as React from 'react'
2
- import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs'
3
- import {createEventHandler} from './utils'
1
+ import {Observable, Subscription} from 'rxjs'
2
+ import {DependencyList, useMemo, useRef, useState} from 'react'
3
+ import {useIsomorphicEffect} from './useIsomorphicEffect'
4
4
 
5
- export function toObservable<T>(value: T): Observable<T>
6
- export function toObservable<T, K>(
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$: Observable<T>): T | undefined
44
- export function useObservable<T>(observable$: Observable<T>, initialValue: T): T
45
- export function useObservable<T>(observable$: Observable<T>, initialValue: () => T): T
46
- export function useObservable<T>(observable$: Observable<T>, initialValue?: T): T | undefined {
47
- const subscription = React.useRef<Subscription>()
48
- const isInitial = React.useRef(true)
49
- const [value, setState] = React.useState<T | undefined>(() => {
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$.subscribe(nextVal => {
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
- React.useEffect(() => {
64
- // when the observable$ changes after initial (possibly sync render)
29
+ useIsomorphicEffect(() => {
30
+ // when the observable changes after initial (possibly sync render)
65
31
  if (!isInitial.current) {
66
- subscription.current = observable$.subscribe(nextVal => setState(nextVal))
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 useObservableState<T>(
80
- initial: T | (() => T),
81
- ): [Observable<T>, React.Dispatch<React.SetStateAction<T>>]
82
-
83
- export function useObservableState<T>(
84
- initial?: T | (() => T),
85
- ): [Observable<T | undefined>, React.Dispatch<React.SetStateAction<T | undefined>>] {
86
- const [value, update] = React.useState<T | undefined>(initial)
87
- return [toObservable(value), update]
88
- }
89
-
90
- export function useObservableContext<T>(context: React.Context<T>): Observable<T> {
91
- return toObservable(React.useContext<T>(context))
92
- }
93
-
94
- type ObservableEventTuple<Event> = [Observable<Event>, (event: Event) => void]
95
-
96
- export function useObservableEvent<Event>(): ObservableEventTuple<Event> {
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
- // for consumption outside of react only
5
- export const createEventHandler = observableCallback
7
+ const createState = <T>(initialState: T) => observableCallback(startWith<T, T>(initialState))
6
8
 
7
- export const createState = <T>(initialState: T) => observableCallback(startWith<T, T>(initialState))
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
+ }
@@ -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
+ }