reactRx.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
  2. if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
  3. if (ar || !(i in from)) {
  4. if (!ar) ar = Array.prototype.slice.call(from, 0, i);
  5. ar[i] = from[i];
  6. }
  7. }
  8. return to.concat(ar || Array.prototype.slice.call(from));
  9. };
  10. import React, { useEffect, useState, createContext, useMemo, useContext, useCallback, useRef, } from 'react';
  11. import { BehaviorSubject } from 'rxjs';
  12. import { RediError } from '@wendellhu/redi';
  13. /**
  14. * unwrap an observable value, return it to the component for rendering, and
  15. * trigger re-render when value changes
  16. *
  17. * **IMPORTANT**. Parent and child components better not subscribe to the same
  18. * observable, otherwise unnecessary re-render would be triggered. Instead, the
  19. * top-most component should subscribe and pass value of the observable to
  20. * its offspring, by props or context. Please consider using `useDependencyContext` and
  21. * `useDependencyContextValue` in this case.
  22. *
  23. * @deprecated Please use `useObservable` instead.
  24. */
  25. export function useDependencyValue(depValue$, defaultValue) {
  26. var firstValue = depValue$ instanceof BehaviorSubject && typeof defaultValue === 'undefined'
  27. ? depValue$.getValue()
  28. : defaultValue;
  29. var _a = useState(firstValue), value = _a[0], setValue = _a[1];
  30. useEffect(function () {
  31. var subscription = depValue$.subscribe(function (val) { return setValue(val); });
  32. return function () { return subscription.unsubscribe(); };
  33. }, [depValue$]);
  34. return value;
  35. }
  36. function unwrap(o) {
  37. if (typeof o === 'function') {
  38. return o();
  39. }
  40. return o;
  41. }
  42. /**
  43. * Subscribe to an observable and return its value. The component will re-render when the observable emits a new value.
  44. *
  45. * @param observable An observable or a function that returns an observable
  46. * @param defaultValue The default value of the observable. It the `observable` can omit an initial value, this value will be neglected.
  47. * @param shouldHaveSyncValue If the observable should have a sync value. If it does not have a sync value, an error will be thrown.
  48. * @param deps A dependency array to decide if we should re-subscribe when the `observable` is a function.
  49. * @returns
  50. */
  51. export function useObservable(observable, defaultValue, shouldHaveSyncValue, deps) {
  52. if (typeof observable === 'function' && !deps) {
  53. throw new RediError("Expected deps to be provided when observable is a function!");
  54. }
  55. var observableRef = useRef(null);
  56. var initializedRef = useRef(false);
  57. // eslint-disable-next-line react-hooks/exhaustive-deps
  58. var destObservable = useMemo(function () { return observable; }, __spreadArray([], (typeof deps !== 'undefined' ? deps : [observable]), true));
  59. // This state is only for trigger React to re-render. We do not use `setValue` directly because it may cause
  60. // memory leaking.
  61. var _a = useState(0), _ = _a[0], setRenderCounter = _a[1];
  62. var valueRef = useRef((function () {
  63. var innerDefaultValue;
  64. if (destObservable) {
  65. var sub = unwrap(destObservable).subscribe(function (value) {
  66. initializedRef.current = true;
  67. innerDefaultValue = value;
  68. });
  69. sub.unsubscribe();
  70. }
  71. return innerDefaultValue !== null && innerDefaultValue !== void 0 ? innerDefaultValue : defaultValue;
  72. })());
  73. useEffect(function () {
  74. var subscription = null;
  75. if (destObservable) {
  76. observableRef.current = unwrap(destObservable);
  77. subscription = observableRef.current.subscribe(function (value) {
  78. valueRef.current = value;
  79. setRenderCounter(function (prev) { return prev + 1; });
  80. });
  81. }
  82. return function () { return subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe(); };
  83. }, [destObservable]);
  84. if (shouldHaveSyncValue && !initializedRef.current) {
  85. throw new Error('Expect `shouldHaveSyncValue` but not getting a sync value!');
  86. }
  87. return valueRef.current;
  88. }
  89. /**
  90. * subscribe to a signal that emits whenever data updates and re-render
  91. *
  92. * @param update$ a signal that the data the functional component depends has updated
  93. */
  94. export function useUpdateBinder(update$) {
  95. var _a = useState(0), dumpSet = _a[1];
  96. useEffect(function () {
  97. var subscription = update$.subscribe(function () { return dumpSet(function (prev) { return prev + 1; }); });
  98. return function () { return subscription.unsubscribe(); };
  99. }, []);
  100. }
  101. var DepValueMapProvider = new WeakMap();
  102. /**
  103. * subscribe to an observable value from a service, creating a context for it so
  104. * it child component won't have to subscribe again and cause unnecessary
  105. */
  106. export function useDependencyContext(depValue$, defaultValue) {
  107. var depRef = useRef(undefined);
  108. var value = useDependencyValue(depValue$, defaultValue);
  109. var Context = useMemo(function () {
  110. return createContext(value);
  111. }, [depValue$]);
  112. var Provider = useCallback(function (props) {
  113. return React.createElement(Context.Provider, { value: value }, props.children);
  114. }, [depValue$, value]);
  115. if (depRef.current !== depValue$) {
  116. if (depRef.current) {
  117. DepValueMapProvider.delete(depRef.current);
  118. }
  119. depRef.current = depValue$;
  120. DepValueMapProvider.set(depValue$, Context);
  121. }
  122. return {
  123. Provider: Provider,
  124. value: value,
  125. };
  126. }
  127. export function useDependencyContextValue(depValue$) {
  128. var context = DepValueMapProvider.get(depValue$);
  129. if (!context) {
  130. throw new RediError("try to read context value but no ancestor component subscribed it.");
  131. }
  132. return useContext(context);
  133. }
  134. //# sourceMappingURL=reactRx.js.map