@sigrea/react 0.2.1 → 0.3.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/README.md +13 -16
- package/dist/index.cjs +15 -11
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +16 -12
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
# @sigrea/react
|
|
2
2
|
|
|
3
|
-
`@sigrea/react` adapts [@sigrea/core](https://www.npmjs.com/package/@sigrea/core)
|
|
3
|
+
`@sigrea/react` adapts [@sigrea/core](https://www.npmjs.com/package/@sigrea/core) molecule modules and signals for use in React components. It binds scope-aware lifecycles to `useEffect`, synchronizes signal subscriptions with React rendering, and provides hooks for both shallow and deep reactivity.
|
|
4
4
|
|
|
5
5
|
- **Signal subscriptions.** `useSignal` subscribes to signals and computed values, triggering re-renders when they change.
|
|
6
6
|
- **Computed subscriptions.** `useComputed` subscribes to computed values and memoizes them per component instance.
|
|
7
7
|
- **Deep signal subscriptions.** `useDeepSignal` subscribes to deep signal objects and exposes them for direct mutation.
|
|
8
|
-
- **
|
|
8
|
+
- **Molecule lifecycles.** `useMolcule` mounts molecule factories and binds their lifecycles to React components.
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
12
12
|
- [Install](#install)
|
|
13
13
|
- [Quick Start](#quick-start)
|
|
14
14
|
- [Consume a Signal](#consume-a-signal)
|
|
15
|
-
- [Bridge Framework-Agnostic
|
|
15
|
+
- [Bridge Framework-Agnostic Molecules](#bridge-framework-agnostic-molecules)
|
|
16
16
|
- [Work with Deep Signals](#work-with-deep-signals)
|
|
17
17
|
- [API Reference](#api-reference)
|
|
18
18
|
- [useSignal](#usesignal)
|
|
19
19
|
- [useComputed](#usecomputed)
|
|
20
20
|
- [useDeepSignal](#usedeepsignal)
|
|
21
|
-
- [
|
|
21
|
+
- [useMolcule](#usemolcule)
|
|
22
22
|
- [Testing](#testing)
|
|
23
23
|
- [Development](#development)
|
|
24
24
|
- [License](#license)
|
|
@@ -47,13 +47,13 @@ export function CounterLabel() {
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
### Bridge Framework-Agnostic
|
|
50
|
+
### Bridge Framework-Agnostic Molecules
|
|
51
51
|
|
|
52
52
|
```tsx
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
53
|
+
import { molecule, signal } from "@sigrea/core";
|
|
54
|
+
import { useMolcule, useSignal } from "@sigrea/react";
|
|
55
55
|
|
|
56
|
-
const
|
|
56
|
+
const CounterMolecule = molecule((props: { initialCount: number }) => {
|
|
57
57
|
const count = signal(props.initialCount);
|
|
58
58
|
|
|
59
59
|
const increment = () => {
|
|
@@ -68,7 +68,7 @@ const CounterLogic = defineLogic<{ initialCount: number }>()((props) => {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
export function Counter(props: { initialCount: number }) {
|
|
71
|
-
const counter =
|
|
71
|
+
const counter = useMolcule(CounterMolcule, props);
|
|
72
72
|
const value = useSignal(counter.count);
|
|
73
73
|
|
|
74
74
|
return (
|
|
@@ -132,27 +132,24 @@ function useDeepSignal<T extends object>(signal: DeepSignal<T>): T
|
|
|
132
132
|
|
|
133
133
|
Exposes a deep signal object for direct mutation within the component. Updates to nested properties trigger re-renders, and the subscription is cleaned up when the component unmounts.
|
|
134
134
|
|
|
135
|
-
###
|
|
135
|
+
### useMolcule
|
|
136
136
|
|
|
137
137
|
```tsx
|
|
138
|
-
function
|
|
139
|
-
|
|
138
|
+
function useMolcule<TProps, TReturn>(
|
|
139
|
+
molecule: MoleculeFactory<TProps, TReturn>,
|
|
140
140
|
props?: TProps
|
|
141
141
|
): TReturn
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Mounts a
|
|
144
|
+
Mounts a molecule factory and returns its public API. The molecule's scope is bound to the component lifecycle: `onMount` callbacks run after the component mounts, and `onUnmount` callbacks run before it unmounts.
|
|
145
145
|
|
|
146
146
|
## Testing
|
|
147
147
|
|
|
148
148
|
```tsx
|
|
149
149
|
// tests/Counter.test.tsx
|
|
150
150
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
151
|
-
import { cleanupLogics } from "@sigrea/core";
|
|
152
151
|
import { Counter } from "../components/Counter";
|
|
153
152
|
|
|
154
|
-
afterEach(() => cleanupLogics());
|
|
155
|
-
|
|
156
153
|
it("increments and displays the updated count", () => {
|
|
157
154
|
render(<Counter initialCount={10} />);
|
|
158
155
|
|
package/dist/index.cjs
CHANGED
|
@@ -3,21 +3,23 @@
|
|
|
3
3
|
const react = require('react');
|
|
4
4
|
const core = require('@sigrea/core');
|
|
5
5
|
|
|
6
|
-
function
|
|
6
|
+
function useMolcule(molecule, ...args) {
|
|
7
7
|
const props = args.length === 0 ? void 0 : args[0];
|
|
8
|
-
const stateRef = react.useRef(
|
|
8
|
+
const stateRef = react.useRef(
|
|
9
|
+
void 0
|
|
10
|
+
);
|
|
9
11
|
const currentState = stateRef.current;
|
|
10
|
-
const shouldRemount = currentState === void 0 || currentState.
|
|
12
|
+
const shouldRemount = currentState === void 0 || currentState.molecule !== molecule || !Object.is(currentState.props, props);
|
|
11
13
|
if (shouldRemount) {
|
|
12
14
|
if (currentState !== void 0) {
|
|
13
15
|
currentState.pendingDisposeToken = null;
|
|
14
|
-
core.
|
|
16
|
+
core.disposeMolecule(currentState.instance);
|
|
15
17
|
stateRef.current = void 0;
|
|
16
18
|
}
|
|
17
|
-
const
|
|
19
|
+
const moleculeArgs = props === void 0 ? [] : [props];
|
|
18
20
|
stateRef.current = {
|
|
19
|
-
instance:
|
|
20
|
-
|
|
21
|
+
instance: molecule(...moleculeArgs),
|
|
22
|
+
molecule,
|
|
21
23
|
props,
|
|
22
24
|
subscribers: 0,
|
|
23
25
|
disposed: false,
|
|
@@ -26,7 +28,9 @@ function useLogic(logic, ...args) {
|
|
|
26
28
|
}
|
|
27
29
|
const state = stateRef.current;
|
|
28
30
|
if (state === void 0) {
|
|
29
|
-
throw new Error(
|
|
31
|
+
throw new Error(
|
|
32
|
+
"useMolcule failed to mount the requested molecule instance."
|
|
33
|
+
);
|
|
30
34
|
}
|
|
31
35
|
const instance = state.instance;
|
|
32
36
|
react.useEffect(() => {
|
|
@@ -42,7 +46,7 @@ function useLogic(logic, ...args) {
|
|
|
42
46
|
return () => {
|
|
43
47
|
const latest = stateRef.current;
|
|
44
48
|
if (latest === void 0 || latest.instance !== instance) {
|
|
45
|
-
core.
|
|
49
|
+
core.disposeMolecule(instance);
|
|
46
50
|
return;
|
|
47
51
|
}
|
|
48
52
|
latest.subscribers -= 1;
|
|
@@ -60,7 +64,7 @@ function useLogic(logic, ...args) {
|
|
|
60
64
|
current.disposed = true;
|
|
61
65
|
current.pendingDisposeToken = null;
|
|
62
66
|
stateRef.current = void 0;
|
|
63
|
-
core.
|
|
67
|
+
core.disposeMolecule(instance);
|
|
64
68
|
});
|
|
65
69
|
}
|
|
66
70
|
};
|
|
@@ -102,6 +106,6 @@ function useDeepSignal(source) {
|
|
|
102
106
|
|
|
103
107
|
exports.useComputed = useComputed;
|
|
104
108
|
exports.useDeepSignal = useDeepSignal;
|
|
105
|
-
exports.
|
|
109
|
+
exports.useMolcule = useMolcule;
|
|
106
110
|
exports.useSignal = useSignal;
|
|
107
111
|
exports.useSnapshot = useSnapshot;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
|
|
2
2
|
|
|
3
|
-
declare function
|
|
3
|
+
declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
|
|
4
4
|
|
|
5
5
|
type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
|
|
6
6
|
declare function useSignal<T>(source: ReadableSignal<T>): T;
|
|
@@ -11,4 +11,4 @@ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): T;
|
|
|
11
11
|
|
|
12
12
|
declare function useSnapshot<T>(handler: SnapshotHandler<T>): T;
|
|
13
13
|
|
|
14
|
-
export { useComputed, useDeepSignal,
|
|
14
|
+
export { useComputed, useDeepSignal, useMolcule, useSignal, useSnapshot };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
|
|
2
2
|
|
|
3
|
-
declare function
|
|
3
|
+
declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
|
|
4
4
|
|
|
5
5
|
type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
|
|
6
6
|
declare function useSignal<T>(source: ReadableSignal<T>): T;
|
|
@@ -11,4 +11,4 @@ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): T;
|
|
|
11
11
|
|
|
12
12
|
declare function useSnapshot<T>(handler: SnapshotHandler<T>): T;
|
|
13
13
|
|
|
14
|
-
export { useComputed, useDeepSignal,
|
|
14
|
+
export { useComputed, useDeepSignal, useMolcule, useSignal, useSnapshot };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoleculeFactory, MoleculeArgs, MoleculeInstance, Signal, ReadonlySignal, Computed, DeepSignal, SnapshotHandler } from '@sigrea/core';
|
|
2
2
|
|
|
3
|
-
declare function
|
|
3
|
+
declare function useMolcule<TReturn extends object, TProps = void>(molecule: MoleculeFactory<TReturn, TProps>, ...args: MoleculeArgs<TProps>): MoleculeInstance<TReturn>;
|
|
4
4
|
|
|
5
5
|
type ReadableSignal<T> = Signal<T> | ReadonlySignal<T>;
|
|
6
6
|
declare function useSignal<T>(source: ReadableSignal<T>): T;
|
|
@@ -11,4 +11,4 @@ declare function useDeepSignal<T extends object>(source: DeepSignal<T>): T;
|
|
|
11
11
|
|
|
12
12
|
declare function useSnapshot<T>(handler: SnapshotHandler<T>): T;
|
|
13
13
|
|
|
14
|
-
export { useComputed, useDeepSignal,
|
|
14
|
+
export { useComputed, useDeepSignal, useMolcule, useSignal, useSnapshot };
|
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { useRef, useEffect, useCallback, useSyncExternalStore, useMemo } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { disposeMolecule, createSignalHandler, createComputedHandler, createDeepSignalHandler } from '@sigrea/core';
|
|
3
3
|
|
|
4
|
-
function
|
|
4
|
+
function useMolcule(molecule, ...args) {
|
|
5
5
|
const props = args.length === 0 ? void 0 : args[0];
|
|
6
|
-
const stateRef = useRef(
|
|
6
|
+
const stateRef = useRef(
|
|
7
|
+
void 0
|
|
8
|
+
);
|
|
7
9
|
const currentState = stateRef.current;
|
|
8
|
-
const shouldRemount = currentState === void 0 || currentState.
|
|
10
|
+
const shouldRemount = currentState === void 0 || currentState.molecule !== molecule || !Object.is(currentState.props, props);
|
|
9
11
|
if (shouldRemount) {
|
|
10
12
|
if (currentState !== void 0) {
|
|
11
13
|
currentState.pendingDisposeToken = null;
|
|
12
|
-
|
|
14
|
+
disposeMolecule(currentState.instance);
|
|
13
15
|
stateRef.current = void 0;
|
|
14
16
|
}
|
|
15
|
-
const
|
|
17
|
+
const moleculeArgs = props === void 0 ? [] : [props];
|
|
16
18
|
stateRef.current = {
|
|
17
|
-
instance:
|
|
18
|
-
|
|
19
|
+
instance: molecule(...moleculeArgs),
|
|
20
|
+
molecule,
|
|
19
21
|
props,
|
|
20
22
|
subscribers: 0,
|
|
21
23
|
disposed: false,
|
|
@@ -24,7 +26,9 @@ function useLogic(logic, ...args) {
|
|
|
24
26
|
}
|
|
25
27
|
const state = stateRef.current;
|
|
26
28
|
if (state === void 0) {
|
|
27
|
-
throw new Error(
|
|
29
|
+
throw new Error(
|
|
30
|
+
"useMolcule failed to mount the requested molecule instance."
|
|
31
|
+
);
|
|
28
32
|
}
|
|
29
33
|
const instance = state.instance;
|
|
30
34
|
useEffect(() => {
|
|
@@ -40,7 +44,7 @@ function useLogic(logic, ...args) {
|
|
|
40
44
|
return () => {
|
|
41
45
|
const latest = stateRef.current;
|
|
42
46
|
if (latest === void 0 || latest.instance !== instance) {
|
|
43
|
-
|
|
47
|
+
disposeMolecule(instance);
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
latest.subscribers -= 1;
|
|
@@ -58,7 +62,7 @@ function useLogic(logic, ...args) {
|
|
|
58
62
|
current.disposed = true;
|
|
59
63
|
current.pendingDisposeToken = null;
|
|
60
64
|
stateRef.current = void 0;
|
|
61
|
-
|
|
65
|
+
disposeMolecule(instance);
|
|
62
66
|
});
|
|
63
67
|
}
|
|
64
68
|
};
|
|
@@ -98,4 +102,4 @@ function useDeepSignal(source) {
|
|
|
98
102
|
return useSnapshot(handler);
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
export { useComputed, useDeepSignal,
|
|
105
|
+
export { useComputed, useDeepSignal, useMolcule, useSignal, useSnapshot };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigrea/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "React adapter bindings for Sigrea
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "React adapter bindings for Sigrea molecule modules.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"packageManager": "pnpm@10.0.0",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"signals",
|
|
37
37
|
"reactivity",
|
|
38
38
|
"react",
|
|
39
|
-
"
|
|
39
|
+
"molecule",
|
|
40
40
|
"typescript"
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"format:fix": "biome check --write ."
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@sigrea/core": "^0.
|
|
55
|
+
"@sigrea/core": "^0.4.0",
|
|
56
56
|
"react": "^18.0.0 || ^19.0.0",
|
|
57
57
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
58
58
|
},
|