synstate-react-hooks-compat 6.1.1 → 6.2.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SynState React hooks (compat)
2
2
 
3
- synstate-react-hooks-compat re-exports all exports from synstate except for `createState`, `createReducer`, and `createBooleanState`, and exports modified versions of those creation functions.
3
+ synstate-react-hooks-compat re-exports every function and type synstate exports except for `createState`, `createReducer`, and `createBooleanState`, and exports modified versions of those creation functions.
4
4
 
5
5
  The first element of the array returned by the `createState<T>` function provided by synstate-react-hooks-compat is changed from `InitializedObservable<T>` to a React hook `() => T`, and the `InitializedObservable<T>` has been moved to the `state` property in the object at index 2.
6
6
 
@@ -1,2 +1,3 @@
1
1
  export { attachIndex, audit, collectToArray, combine, combineLatest, counter, createAsyncChildObservable, createEventEmitter, createInitializedSyncChildObservable, createRootObservable, createScope, createSyncChildObservable, createValueEmitter, debounce, derive, distinctUntilChanged, empty, filter, flatMap, fromAbortablePromise, fromPromise, fromSubscribable, getCurrentScope, getKey, isChildObservable, isManagerObservable, isObservable, isRootObservable, just, keepEqualParts, map, mapOptional, mapResultErr, mapResultOk, mapTo, merge, mergeMap, onScopeDispose, pairwise, pluck, scan, skip, skipIfNoChange, skipUntil, skipWhile, source, switchMap, take, takeUntil, takeWhile, throttle, timer, untracked, unwrapOptional, unwrapResultErr, unwrapResultOk, withBuffered, withBufferedFrom, withCurrentValueFrom, withIndex, withInitialValue, withLatestFrom, zip, } from 'synstate';
2
+ export type { AsyncChildObservable, AsyncChildObservableHooks, AsyncChildObservableTools, AuditOperatorObservable, ChildObservable, ChildObservableConfig, ChildObservableKind, CombineObservable, CombineObservableRefined, CounterObservable, DebounceOperatorObservable, DeriveObservable, DeriveObservableRefined, DropInitialValueOperator, FilterOperatorObservable, FromPromiseObservable, FromSubscribableObservable, InitializedAsyncChildObservable, InitializedChildObservableConfig, InitializedObservable, InitializedRootObservable, InitializedSourceObservable, InitializedSyncChildObservable, KeepEqualPartsOperatorObservable, KeepInitialValueOperator, ManagerObservable, ManagerObservableKind, MapOperatorObservable, MergeMapOperatorObservable, MergeObservable, MergeObservableRefined, NonEmptyUnknownList, Observable, ObservableBase, ObservableKind, ObservableValue, Operator, PairwiseOperatorObservable, RootObservable, RootObservableConfig, RootObservableTools, ScanOperatorObservable, Scope, SkipIfNoChangeOperatorObservable, SkipUntilOperatorObservable, SkipWhileOperatorObservable, SourceObservable, StateOptions, Subscribable, Subscriber, Subscription, SwitchMapOperatorObservable, SyncChildObservable, SyncChildObservableHooks, SyncChildObservableTools, TakeOperatorObservable, TakeUntilOperatorObservable, TakeWhileOperatorObservable, ThrottleOperatorObservable, TimerId, TimerObservable, TupleToQueueTuple, Unwrap, UpdateToken, WithBufferedFromOperatorObservable, WithCurrentValueFromOperatorObservable, WithInitialValueOperator, WithInitialValueOperatorObservable, Wrap, WrapInitialized, ZipObservable, ZipObservableRefined, } from 'synstate';
2
3
  //# sourceMappingURL=synstate.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"synstate.d.mts","sourceRoot":"","sources":["../src/synstate.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,KAAK,EACL,cAAc,EACd,OAAO,EACP,aAAa,EACb,OAAO,EACP,0BAA0B,EAC1B,kBAAkB,EAClB,oCAAoC,EACpC,oBAAoB,EACpB,WAAW,EACX,yBAAyB,EACzB,kBAAkB,EAClB,QAAQ,EACR,MAAM,EACN,oBAAoB,EACpB,KAAK,EACL,MAAM,EACN,OAAO,EACP,oBAAoB,EACpB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,MAAM,EACN,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,IAAI,EACJ,cAAc,EACd,GAAG,EACH,WAAW,EACX,YAAY,EACZ,WAAW,EACX,KAAK,EACL,KAAK,EACL,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,cAAc,EACd,SAAS,EACT,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAI,EACJ,SAAS,EACT,SAAS,EACT,QAAQ,EACR,KAAK,EACL,SAAS,EACT,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,GAAG,GACJ,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"synstate.d.mts","sourceRoot":"","sources":["../src/synstate.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,KAAK,EACL,cAAc,EACd,OAAO,EACP,aAAa,EACb,OAAO,EACP,0BAA0B,EAC1B,kBAAkB,EAClB,oCAAoC,EACpC,oBAAoB,EACpB,WAAW,EACX,yBAAyB,EACzB,kBAAkB,EAClB,QAAQ,EACR,MAAM,EACN,oBAAoB,EACpB,KAAK,EACL,MAAM,EACN,OAAO,EACP,oBAAoB,EACpB,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,MAAM,EACN,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,IAAI,EACJ,cAAc,EACd,GAAG,EACH,WAAW,EACX,YAAY,EACZ,WAAW,EACX,KAAK,EACL,KAAK,EACL,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,cAAc,EACd,SAAS,EACT,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAI,EACJ,SAAS,EACT,SAAS,EACT,QAAQ,EACR,KAAK,EACL,SAAS,EACT,cAAc,EACd,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,GAAG,GACJ,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,0BAA0B,EAC1B,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,+BAA+B,EAC/B,gCAAgC,EAChC,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,8BAA8B,EAC9B,gCAAgC,EAChC,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,eAAe,EACf,sBAAsB,EACtB,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,QAAQ,EACR,0BAA0B,EAC1B,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,EACL,gCAAgC,EAChC,2BAA2B,EAC3B,2BAA2B,EAC3B,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,EAC1B,OAAO,EACP,eAAe,EACf,iBAAiB,EACjB,MAAM,EACN,WAAW,EACX,kCAAkC,EAClC,sCAAsC,EACtC,wBAAwB,EACxB,kCAAkC,EAClC,IAAI,EACJ,eAAe,EACf,aAAa,EACb,oBAAoB,GACrB,MAAM,UAAU,CAAC"}
@@ -1,3 +1,17 @@
1
1
  import type { Observable } from 'synstate';
2
+ /**
3
+ * Subscribes `subscriptionFn` to `observable$` while the component is mounted,
4
+ * and unsubscribes when it unmounts.
5
+ *
6
+ * Each value is passed to the `subscriptionFn` of the latest render, so it sees
7
+ * the current props and state; a new function on every render does not
8
+ * re-subscribe. Passing a different Observable moves the subscription to it,
9
+ * so pass one that keeps its identity across renders (made outside the
10
+ * component, or memoized): one built during render is subscribed again, and an
11
+ * initialized one calls `subscriptionFn` again, on every render.
12
+ *
13
+ * @param observable$ The Observable to subscribe to.
14
+ * @param subscriptionFn Called with each value `observable$` emits.
15
+ */
2
16
  export declare const useObservableEffect: <A>(observable$: Observable<A>, subscriptionFn: (v: A) => void) => void;
3
17
  //# sourceMappingURL=use-observable-effect.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-observable-effect.d.mts","sourceRoot":"","sources":["../src/use-observable-effect.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,mBAAmB,GAAI,CAAC,eACtB,UAAU,CAAC,CAAC,CAAC,kBACV,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,KAC7B,IAQF,CAAC"}
1
+ {"version":3,"file":"use-observable-effect.d.mts","sourceRoot":"","sources":["../src/use-observable-effect.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,eACtB,UAAU,CAAC,CAAC,CAAC,kBACV,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,KAC7B,IAmBF,CAAC"}
@@ -1,10 +1,41 @@
1
1
  import * as React from 'react';
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
2
16
  export const useObservableEffect = (observable$, subscriptionFn) => {
17
+ const mut_subscriptionFnRef = React.useRef(subscriptionFn);
18
+
19
+
20
+
21
+ useBeforeEffects(() => {
22
+ mut_subscriptionFnRef.current = subscriptionFn;
23
+ });
3
24
  React.useEffect(() => {
4
- const s = observable$.subscribe(subscriptionFn);
25
+ const s = observable$.subscribe((v) => {
26
+ mut_subscriptionFnRef.current(v);
27
+ });
5
28
  return () => {
6
29
  s.unsubscribe();
7
30
  };
8
- }, []);
31
+ }, [observable$]);
9
32
  };
33
+
34
+
35
+
36
+
37
+ const useBeforeEffects =
38
+
39
+ React.useInsertionEffect ??
40
+ (typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect);
10
41
  //# sourceMappingURL=use-observable-effect.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-observable-effect.mjs","sourceRoot":"","sources":["../src/use-observable-effect.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,WAA0B,EAC1B,cAA8B,EACxB,EAAE;IACR,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAEhD,OAAO,GAAG,EAAE;YACV,CAAC,CAAC,WAAW,EAAE,CAAC;QAClB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC,CAAC"}
1
+ {"version":3,"file":"use-observable-effect.mjs","sourceRoot":"","sources":["../src/use-observable-effect.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,WAA0B,EAC1B,cAA8B,EACxB,EAAE;IACR,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAE3D,2EAA2E;IAC3E,qEAAqE;IACrE,oBAAoB;IACpB,gBAAgB,CAAC,GAAG,EAAE;QACpB,qBAAqB,CAAC,OAAO,GAAG,cAAc,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE;YACpC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,CAAC,CAAC,WAAW,EAAE,CAAC;QAClB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC;AAEF,8EAA8E;AAC9E,wEAAwE;AACxE,8EAA8E;AAC9E,0CAA0C;AAC1C,MAAM,gBAAgB;AACpB,4HAA4H;AAC5H,KAAK,CAAC,kBAAkB;IACxB,CAAC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC"}
@@ -1,3 +1,17 @@
1
1
  import { type Observable } from 'synstate';
2
+ /**
3
+ * Returns an observable that holds `input` and emits each new `input` the
4
+ * component renders with. It completes when the component unmounts, in a timer
5
+ * of 0 ms.
6
+ *
7
+ * React 18 and later run an effect's cleanup without unmounting in two cases.
8
+ * StrictMode runs it once after mount in development and sets the effect up
9
+ * again at once; completing only in a timer after the cleanup lets that setup
10
+ * keep the same observable. `<Activity mode="hidden">` (React 19.2) runs it and
11
+ * keeps the component's state; the observable completes, and is replaced with a
12
+ * new one holding the latest value when the component renders or is shown
13
+ * again. So read the observable from each render rather than keeping the first
14
+ * one.
15
+ */
2
16
  export declare const useValueAsObservable: <A>(input: A) => Observable<A>;
3
17
  //# sourceMappingURL=use-value-as-observable.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-value-as-observable.d.mts","sourceRoot":"","sources":["../src/use-value-as-observable.mts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,UAAU,EAEhB,MAAM,UAAU,CAAC;AAElB,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAU,CAAC,KAAG,UAAU,CAAC,CAAC,CA0B/D,CAAC"}
1
+ {"version":3,"file":"use-value-as-observable.d.mts","sourceRoot":"","sources":["../src/use-value-as-observable.mts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,UAAU,EAEhB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAU,CAAC,KAAG,UAAU,CAAC,CAAC,CAiD/D,CAAC"}
@@ -1,18 +1,58 @@
1
1
  import * as React from 'react';
2
2
  import { source, withInitialValue, } from 'synstate';
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
3
17
  export const useValueAsObservable = (input) => {
4
- const sourceObservable = React.useMemo(source, []);
5
- const state = React.useMemo(() => sourceObservable.pipe(withInitialValue(input)), []);
6
- React.useEffect(() => () => {
7
- state.complete();
8
- }, []);
9
- const setState = React.useCallback((nextState) => {
10
- sourceObservable.next(nextState);
11
- return nextState;
12
- }, []);
18
+ const [observables, setObservables] = React.useState(() => createObservables(input));
19
+
20
+ const mut_setups = React.useRef(0);
21
+
22
+ if (observables.state.isCompleted) {
23
+ setObservables(createObservables(input));
24
+ }
25
+
26
+
13
27
  React.useEffect(() => {
14
- setState(input);
15
- }, [input]);
16
- return state;
28
+ mut_setups.current += 1;
29
+ if (observables.state.isCompleted) {
30
+
31
+
32
+
33
+ setObservables(createObservables(input));
34
+ return undefined;
35
+ }
36
+ return () => {
37
+ const setups = mut_setups.current;
38
+ setTimeout(() => {
39
+ if (mut_setups.current === setups) {
40
+ observables.state.complete();
41
+ }
42
+ }, 0);
43
+ };
44
+ }, [observables, input]);
45
+ React.useEffect(() => {
46
+
47
+ if (!observables.state.isCompleted &&
48
+ !Object.is(observables.state.getSnapshot().value, input)) {
49
+ observables.source.next(input);
50
+ }
51
+ }, [input, observables]);
52
+ return observables.state;
53
+ };
54
+ const createObservables = (input) => {
55
+ const source$ = source();
56
+ return { source: source$, state: source$.pipe(withInitialValue(input)) };
17
57
  };
18
58
  //# sourceMappingURL=use-value-as-observable.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-value-as-observable.mjs","sourceRoot":"","sources":["../src/use-value-as-observable.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EACL,MAAM,EACN,gBAAgB,GAGjB,MAAM,UAAU,CAAC;AAElB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAK,KAAQ,EAAiB,EAAE;IAClE,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAsB,MAAM,EAAE,EAAE,CAAC,CAAC;IAExE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,GAAG,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EACpD,EAAE,CACH,CAAC;IAEF,KAAK,CAAC,SAAS,CACb,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,KAAK,CAAC,QAAQ,EAAE,CAAC;IACnB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAY,EAAK,EAAE;QACrD,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEjC,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
1
+ {"version":3,"file":"use-value-as-observable.mjs","sourceRoot":"","sources":["../src/use-value-as-observable.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EACL,MAAM,EACN,gBAAgB,GAIjB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAK,KAAQ,EAAiB,EAAE;IAClE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CACxD,iBAAiB,CAAC,KAAK,CAAC,CACzB,CAAC;IAEF,8EAA8E;IAC9E,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAEnC,+EAA+E;IAC/E,IAAI,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAClC,cAAc,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,4EAA4E;IAC5E,sDAAsD;IACtD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;QAExB,IAAI,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YAClC,gEAAgE;YAChE,oEAAoE;YACpE,8CAA8C;YAC9C,cAAc,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;YAEzC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,GAAG,EAAE;YACV,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC;YAElC,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,UAAU,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;oBAClC,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAC/B,CAAC;YACH,CAAC,EAAE,CAAC,CAAC,CAAC;QACR,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAEzB,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,8DAA8D;QAC9D,IACE,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW;YAC9B,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EACxD,CAAC;YACD,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC;IAEzB,OAAO,WAAW,CAAC,KAAK,CAAC;AAC3B,CAAC,CAAC;AAOF,MAAM,iBAAiB,GAAG,CAAK,KAAQ,EAAkB,EAAE;IACzD,MAAM,OAAO,GAAG,MAAM,EAAK,CAAC;IAE5B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;AAC3E,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synstate-react-hooks-compat",
3
- "version": "6.1.1",
3
+ "version": "6.2.1",
4
4
  "description": "SynState React hooks for React 16.8–17 (without useSyncExternalStore)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,8 +28,8 @@
28
28
  "LICENSE"
29
29
  ],
30
30
  "dependencies": {
31
- "synstate": "6.1.1",
32
- "ts-data-forge": "^14.10.0",
31
+ "synstate": "6.2.1",
32
+ "ts-data-forge": "^14.10.2",
33
33
  "ts-type-forge": "^9.2.2"
34
34
  },
35
35
  "devDependencies": {
@@ -38,10 +38,10 @@
38
38
  "@types/react": "19.3.0",
39
39
  "@types/react-dom": "19.3.0",
40
40
  "@vitest/browser-playwright": "5.0.5",
41
- "eslint-config-typed": "7.1.0",
42
- "eslint-plugin-ts-data-forge": "0.10.5",
43
- "eslint-plugin-ts-fortress": "1.3.10",
44
- "eslint-plugin-ts-type-forge": "0.8.15",
41
+ "eslint-config-typed": "7.3.1",
42
+ "eslint-plugin-ts-data-forge": "0.10.8",
43
+ "eslint-plugin-ts-fortress": "1.3.12",
44
+ "eslint-plugin-ts-type-forge": "0.8.17",
45
45
  "playwright": "1.64.1",
46
46
  "prettier": "3.9.10",
47
47
  "prettier-plugin-organize-imports": "4.3.0",
package/src/synstate.mts CHANGED
@@ -63,3 +63,78 @@ export {
63
63
  withLatestFrom,
64
64
  zip,
65
65
  } from 'synstate';
66
+
67
+ export type {
68
+ AsyncChildObservable,
69
+ AsyncChildObservableHooks,
70
+ AsyncChildObservableTools,
71
+ AuditOperatorObservable,
72
+ ChildObservable,
73
+ ChildObservableConfig,
74
+ ChildObservableKind,
75
+ CombineObservable,
76
+ CombineObservableRefined,
77
+ CounterObservable,
78
+ DebounceOperatorObservable,
79
+ DeriveObservable,
80
+ DeriveObservableRefined,
81
+ DropInitialValueOperator,
82
+ FilterOperatorObservable,
83
+ FromPromiseObservable,
84
+ FromSubscribableObservable,
85
+ InitializedAsyncChildObservable,
86
+ InitializedChildObservableConfig,
87
+ InitializedObservable,
88
+ InitializedRootObservable,
89
+ InitializedSourceObservable,
90
+ InitializedSyncChildObservable,
91
+ KeepEqualPartsOperatorObservable,
92
+ KeepInitialValueOperator,
93
+ ManagerObservable,
94
+ ManagerObservableKind,
95
+ MapOperatorObservable,
96
+ MergeMapOperatorObservable,
97
+ MergeObservable,
98
+ MergeObservableRefined,
99
+ NonEmptyUnknownList,
100
+ Observable,
101
+ ObservableBase,
102
+ ObservableKind,
103
+ ObservableValue,
104
+ Operator,
105
+ PairwiseOperatorObservable,
106
+ RootObservable,
107
+ RootObservableConfig,
108
+ RootObservableTools,
109
+ ScanOperatorObservable,
110
+ Scope,
111
+ SkipIfNoChangeOperatorObservable,
112
+ SkipUntilOperatorObservable,
113
+ SkipWhileOperatorObservable,
114
+ SourceObservable,
115
+ StateOptions,
116
+ Subscribable,
117
+ Subscriber,
118
+ Subscription,
119
+ SwitchMapOperatorObservable,
120
+ SyncChildObservable,
121
+ SyncChildObservableHooks,
122
+ SyncChildObservableTools,
123
+ TakeOperatorObservable,
124
+ TakeUntilOperatorObservable,
125
+ TakeWhileOperatorObservable,
126
+ ThrottleOperatorObservable,
127
+ TimerId,
128
+ TimerObservable,
129
+ TupleToQueueTuple,
130
+ Unwrap,
131
+ UpdateToken,
132
+ WithBufferedFromOperatorObservable,
133
+ WithCurrentValueFromOperatorObservable,
134
+ WithInitialValueOperator,
135
+ WithInitialValueOperatorObservable,
136
+ Wrap,
137
+ WrapInitialized,
138
+ ZipObservable,
139
+ ZipObservableRefined,
140
+ } from 'synstate';
@@ -1,15 +1,49 @@
1
1
  import * as React from 'react';
2
2
  import type { Observable } from 'synstate';
3
3
 
4
+ /**
5
+ * Subscribes `subscriptionFn` to `observable$` while the component is mounted,
6
+ * and unsubscribes when it unmounts.
7
+ *
8
+ * Each value is passed to the `subscriptionFn` of the latest render, so it sees
9
+ * the current props and state; a new function on every render does not
10
+ * re-subscribe. Passing a different Observable moves the subscription to it,
11
+ * so pass one that keeps its identity across renders (made outside the
12
+ * component, or memoized): one built during render is subscribed again, and an
13
+ * initialized one calls `subscriptionFn` again, on every render.
14
+ *
15
+ * @param observable$ The Observable to subscribe to.
16
+ * @param subscriptionFn Called with each value `observable$` emits.
17
+ */
4
18
  export const useObservableEffect = <A,>(
5
19
  observable$: Observable<A>,
6
20
  subscriptionFn: (v: A) => void,
7
21
  ): void => {
22
+ const mut_subscriptionFnRef = React.useRef(subscriptionFn);
23
+
24
+ // On React 18 and later, runs before the layout and passive effects of the
25
+ // commit, so a value one of them emits already reaches this render's
26
+ // `subscriptionFn`.
27
+ useBeforeEffects(() => {
28
+ mut_subscriptionFnRef.current = subscriptionFn;
29
+ });
30
+
8
31
  React.useEffect(() => {
9
- const s = observable$.subscribe(subscriptionFn);
32
+ const s = observable$.subscribe((v) => {
33
+ mut_subscriptionFnRef.current(v);
34
+ });
10
35
 
11
36
  return () => {
12
37
  s.unsubscribe();
13
38
  };
14
- }, []);
39
+ }, [observable$]);
15
40
  };
41
+
42
+ // `useInsertionEffect` is React 18's (`'useInsertionEffect' in React` narrows
43
+ // to `never`, as the types have it). Before it, `useLayoutEffect` comes
44
+ // closest, running before the passive effects only; it warns when rendered on
45
+ // the server, where nothing emits anyway.
46
+ const useBeforeEffects: typeof React.useLayoutEffect =
47
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- React 16 and 17 have none, whatever the types say
48
+ React.useInsertionEffect ??
49
+ (typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect);
@@ -2,34 +2,83 @@ import * as React from 'react';
2
2
  import {
3
3
  source,
4
4
  withInitialValue,
5
+ type InitializedObservable,
5
6
  type Observable,
6
7
  type SourceObservable,
7
8
  } from 'synstate';
8
9
 
10
+ /**
11
+ * Returns an observable that holds `input` and emits each new `input` the
12
+ * component renders with. It completes when the component unmounts, in a timer
13
+ * of 0 ms.
14
+ *
15
+ * React 18 and later run an effect's cleanup without unmounting in two cases.
16
+ * StrictMode runs it once after mount in development and sets the effect up
17
+ * again at once; completing only in a timer after the cleanup lets that setup
18
+ * keep the same observable. `<Activity mode="hidden">` (React 19.2) runs it and
19
+ * keeps the component's state; the observable completes, and is replaced with a
20
+ * new one holding the latest value when the component renders or is shown
21
+ * again. So read the observable from each render rather than keeping the first
22
+ * one.
23
+ */
9
24
  export const useValueAsObservable = <A,>(input: A): Observable<A> => {
10
- const sourceObservable = React.useMemo<SourceObservable<A>>(source, []);
11
-
12
- const state = React.useMemo(
13
- () => sourceObservable.pipe(withInitialValue(input)),
14
- [],
25
+ const [observables, setObservables] = React.useState(() =>
26
+ createObservables(input),
15
27
  );
16
28
 
17
- React.useEffect(
18
- () => () => {
19
- state.complete();
20
- },
21
- [],
22
- );
29
+ /** Bumped by every setup, so that a completion scheduled before it lapses. */
30
+ const mut_setups = React.useRef(0);
31
+
32
+ // Replaced during render, so that what renders below gets the new one at once.
33
+ if (observables.state.isCompleted) {
34
+ setObservables(createObservables(input));
35
+ }
36
+
37
+ // A new `input` runs this effect's cleanup and setup again at once, and the
38
+ // setup cancels the completion the cleanup scheduled.
39
+ React.useEffect(() => {
40
+ mut_setups.current += 1;
41
+
42
+ if (observables.state.isCompleted) {
43
+ // Shown again with no render: start from the latest input React
44
+ // committed, which a render while hidden may have changed after the
45
+ // completed observable stopped taking values.
46
+ setObservables(createObservables(input));
23
47
 
24
- const setState = React.useCallback((nextState: A): A => {
25
- sourceObservable.next(nextState);
48
+ return undefined;
49
+ }
26
50
 
27
- return nextState;
28
- }, []);
51
+ return () => {
52
+ const setups = mut_setups.current;
53
+
54
+ setTimeout(() => {
55
+ if (mut_setups.current === setups) {
56
+ observables.state.complete();
57
+ }
58
+ }, 0);
59
+ };
60
+ }, [observables, input]);
29
61
 
30
62
  React.useEffect(() => {
31
- setState(input);
32
- }, [input]);
63
+ // The observable already holds the value it was created with.
64
+ if (
65
+ !observables.state.isCompleted &&
66
+ !Object.is(observables.state.getSnapshot().value, input)
67
+ ) {
68
+ observables.source.next(input);
69
+ }
70
+ }, [input, observables]);
71
+
72
+ return observables.state;
73
+ };
74
+
75
+ type Observables<A> = Readonly<{
76
+ source: SourceObservable<A>;
77
+ state: InitializedObservable<A>;
78
+ }>;
79
+
80
+ const createObservables = <A,>(input: A): Observables<A> => {
81
+ const source$ = source<A>();
33
82
 
34
- return state;
83
+ return { source: source$, state: source$.pipe(withInitialValue(input)) };
35
84
  };