@hoosk/minisignals-react 1.0.3

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/dist/index.cjs ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ useSignal: () => useSignal,
24
+ useSignalValue: () => useSignalValue
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/useSignalValue.ts
29
+ var import_react = require("react");
30
+ var import_minisignals = require("@hoosk/minisignals");
31
+ function useSignalValue(sig) {
32
+ const subscribe = (0, import_react.useCallback)(
33
+ (onStoreChange) => {
34
+ let isInitialRun = true;
35
+ const unsubscribe = (0, import_minisignals.effect)(() => {
36
+ const _val = sig.value;
37
+ if (!isInitialRun) {
38
+ onStoreChange();
39
+ }
40
+ isInitialRun = false;
41
+ });
42
+ return unsubscribe;
43
+ },
44
+ [sig]
45
+ );
46
+ return (0, import_react.useSyncExternalStore)(
47
+ subscribe,
48
+ () => sig.value,
49
+ // getSnapshot
50
+ () => sig.value
51
+ // getServerSnapshot (SSR support)
52
+ );
53
+ }
54
+
55
+ // src/useSignal.ts
56
+ var import_react2 = require("react");
57
+ var import_minisignals2 = require("@hoosk/minisignals");
58
+ function useSignal(initialValue) {
59
+ const signalRef = (0, import_react2.useRef)(null);
60
+ if (signalRef.current === null) {
61
+ signalRef.current = (0, import_minisignals2.signal)(initialValue);
62
+ }
63
+ return signalRef.current;
64
+ }
65
+ // Annotate the CommonJS export names for ESM import in node:
66
+ 0 && (module.exports = {
67
+ useSignal,
68
+ useSignalValue
69
+ });
@@ -0,0 +1,23 @@
1
+ import { Signal, Computed } from '@hoosk/minisignals';
2
+
3
+ /**
4
+ * A hook that subscribes to a signal or computed value and triggers
5
+ * a React re-render whenever the value changes.
6
+ * Uses React 18's useSyncExternalStore for tear-free concurrent rendering.
7
+ *
8
+ * @param sig The Signal or Computed to observe.
9
+ * @returns The current value of the signal.
10
+ */
11
+ declare function useSignalValue<T>(sig: Signal<T> | Computed<T>): T;
12
+
13
+ /**
14
+ * Creates a signal that is tied to the component's lifecycle.
15
+ * The signal instance is preserved across re-renders.
16
+ * NOTE: To trigger a re-render on value change, you must use `useSignalValue(sig)`.
17
+ *
18
+ * @param initialValue The starting value of the signal.
19
+ * @returns A stable Signal instance.
20
+ */
21
+ declare function useSignal<T>(initialValue: T): Signal<T>;
22
+
23
+ export { useSignal, useSignalValue };
@@ -0,0 +1,23 @@
1
+ import { Signal, Computed } from '@hoosk/minisignals';
2
+
3
+ /**
4
+ * A hook that subscribes to a signal or computed value and triggers
5
+ * a React re-render whenever the value changes.
6
+ * Uses React 18's useSyncExternalStore for tear-free concurrent rendering.
7
+ *
8
+ * @param sig The Signal or Computed to observe.
9
+ * @returns The current value of the signal.
10
+ */
11
+ declare function useSignalValue<T>(sig: Signal<T> | Computed<T>): T;
12
+
13
+ /**
14
+ * Creates a signal that is tied to the component's lifecycle.
15
+ * The signal instance is preserved across re-renders.
16
+ * NOTE: To trigger a re-render on value change, you must use `useSignalValue(sig)`.
17
+ *
18
+ * @param initialValue The starting value of the signal.
19
+ * @returns A stable Signal instance.
20
+ */
21
+ declare function useSignal<T>(initialValue: T): Signal<T>;
22
+
23
+ export { useSignal, useSignalValue };
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ // src/useSignalValue.ts
2
+ import { useSyncExternalStore, useCallback } from "react";
3
+ import { effect } from "@hoosk/minisignals";
4
+ function useSignalValue(sig) {
5
+ const subscribe = useCallback(
6
+ (onStoreChange) => {
7
+ let isInitialRun = true;
8
+ const unsubscribe = effect(() => {
9
+ const _val = sig.value;
10
+ if (!isInitialRun) {
11
+ onStoreChange();
12
+ }
13
+ isInitialRun = false;
14
+ });
15
+ return unsubscribe;
16
+ },
17
+ [sig]
18
+ );
19
+ return useSyncExternalStore(
20
+ subscribe,
21
+ () => sig.value,
22
+ // getSnapshot
23
+ () => sig.value
24
+ // getServerSnapshot (SSR support)
25
+ );
26
+ }
27
+
28
+ // src/useSignal.ts
29
+ import { useRef } from "react";
30
+ import { signal } from "@hoosk/minisignals";
31
+ function useSignal(initialValue) {
32
+ const signalRef = useRef(null);
33
+ if (signalRef.current === null) {
34
+ signalRef.current = signal(initialValue);
35
+ }
36
+ return signalRef.current;
37
+ }
38
+ export {
39
+ useSignal,
40
+ useSignalValue
41
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@hoosk/minisignals-react",
3
+ "version": "1.0.3",
4
+ "description": "React adapter for @hoosk/minisignals",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "peerDependencies": {
10
+ "react": ">=18.0.0"
11
+ },
12
+ "dependencies": {
13
+ "@hoosk/minisignals": "*"
14
+ },
15
+ "devDependencies": {
16
+ "@testing-library/react": "^14.2.1",
17
+ "@types/node": "^24.6.0",
18
+ "@types/react": "^18.2.0",
19
+ "jsdom": "^24.0.0",
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
+ "tsup": "^8.5.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
26
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
27
+ "test": "vitest run"
28
+ },
29
+ "author": "Hoosk",
30
+ "license": "MIT"
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './useSignalValue';
2
+ export * from './useSignal';
@@ -0,0 +1,20 @@
1
+ import { useRef } from 'react';
2
+ import { signal, Signal } from '@hoosk/minisignals';
3
+
4
+ /**
5
+ * Creates a signal that is tied to the component's lifecycle.
6
+ * The signal instance is preserved across re-renders.
7
+ * NOTE: To trigger a re-render on value change, you must use `useSignalValue(sig)`.
8
+ *
9
+ * @param initialValue The starting value of the signal.
10
+ * @returns A stable Signal instance.
11
+ */
12
+ export function useSignal<T>(initialValue: T): Signal<T> {
13
+ const signalRef = useRef<Signal<T> | null>(null);
14
+
15
+ if (signalRef.current === null) {
16
+ signalRef.current = signal(initialValue);
17
+ }
18
+
19
+ return signalRef.current;
20
+ }
@@ -0,0 +1,36 @@
1
+ import { useSyncExternalStore, useCallback } from 'react';
2
+ import { effect, Signal, Computed } from '@hoosk/minisignals';
3
+
4
+ /**
5
+ * A hook that subscribes to a signal or computed value and triggers
6
+ * a React re-render whenever the value changes.
7
+ * Uses React 18's useSyncExternalStore for tear-free concurrent rendering.
8
+ *
9
+ * @param sig The Signal or Computed to observe.
10
+ * @returns The current value of the signal.
11
+ */
12
+ export function useSignalValue<T>(sig: Signal<T> | Computed<T>): T {
13
+ const subscribe = useCallback(
14
+ (onStoreChange: () => void) => {
15
+ let isInitialRun = true;
16
+
17
+ const unsubscribe = effect(() => {
18
+ const _val = sig.value;
19
+
20
+ if (!isInitialRun) {
21
+ onStoreChange();
22
+ }
23
+ isInitialRun = false;
24
+ });
25
+
26
+ return unsubscribe;
27
+ },
28
+ [sig]
29
+ );
30
+
31
+ return useSyncExternalStore(
32
+ subscribe,
33
+ () => sig.value, // getSnapshot
34
+ () => sig.value // getServerSnapshot (SSR support)
35
+ );
36
+ }
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render, screen, act } from '@testing-library/react';
4
+ import { signal, computed } from '@hoosk/minisignals';
5
+ import { useSignalValue, useSignal } from '../src/index.ts';
6
+
7
+ describe('@hoosk/minisignals-react adapter', () => {
8
+ describe('useSignalValue()', () => {
9
+ it('should render the initial value of a signal', () => {
10
+ const count = signal(5);
11
+
12
+ const TestComponent = () => {
13
+ const val = useSignalValue(count);
14
+ return <div>Value: {val}</div>;
15
+ };
16
+
17
+ render(<TestComponent />);
18
+ expect(screen.getByText('Value: 5')).toBeDefined();
19
+ });
20
+
21
+ it('should trigger a re-render when the signal mutates', () => {
22
+ const count = signal(10);
23
+
24
+ const TestComponent = () => {
25
+ const val = useSignalValue(count);
26
+ return <div data-testid="display">{val}</div>;
27
+ };
28
+
29
+ render(<TestComponent />);
30
+ const display = screen.getByTestId('display');
31
+ expect(display.textContent).toBe('10');
32
+
33
+ act(() => {
34
+ count.value = 20;
35
+ });
36
+
37
+ expect(display.textContent).toBe('20');
38
+ });
39
+
40
+ it('should work seamlessly with computed signals', () => {
41
+ const name = signal('A');
42
+ const doubledName = computed(() => name.value + name.value);
43
+
44
+ const TestComponent = () => {
45
+ const val = useSignalValue(doubledName);
46
+ return <div data-testid="computed-display">{val}</div>;
47
+ };
48
+
49
+ render(<TestComponent />);
50
+ const display = screen.getByTestId('computed-display');
51
+ expect(display.textContent).toBe('AA');
52
+
53
+ act(() => {
54
+ name.value = 'B';
55
+ });
56
+
57
+ expect(display.textContent).toBe('BB');
58
+ });
59
+ });
60
+
61
+ describe('useSignal()', () => {
62
+ it('should maintain state across re-renders', () => {
63
+ let renderCount = 0;
64
+
65
+ const TestComponent = () => {
66
+ renderCount++;
67
+ // Create local signal
68
+ const localCount = useSignal(100);
69
+ // Subscribe to it to trigger re-renders
70
+ const val = useSignalValue(localCount);
71
+
72
+ return (
73
+ <div>
74
+ <span data-testid="val">{val}</span>
75
+ <button onClick={() => localCount.value++}>Add</button>
76
+ </div>
77
+ );
78
+ };
79
+
80
+ render(<TestComponent />);
81
+
82
+ const valSpan = screen.getByTestId('val');
83
+ const btn = screen.getByText('Add');
84
+
85
+ expect(valSpan.textContent).toBe('100');
86
+ expect(renderCount).toBe(1);
87
+
88
+ act(() => {
89
+ btn.click();
90
+ });
91
+
92
+ // The hook maintained the signal instance, and useSignalValue triggered the re-render
93
+ expect(valSpan.textContent).toBe('101');
94
+ expect(renderCount).toBe(2);
95
+ });
96
+ });
97
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist"
5
+ },
6
+ "include": ["src/**/*"]
7
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'jsdom',
6
+ globals: true,
7
+ },
8
+ resolve: {
9
+ alias: {
10
+ '@hoosk/minisignals': new URL('../core/src/index.ts', import.meta.url).pathname,
11
+ },
12
+ },
13
+ });