Skip to content

Commit 0df78b9

Browse files
committed
Add react-utils package
1 parent 9022d4a commit 0df78b9

File tree

7 files changed

+168
-15
lines changed

7 files changed

+168
-15
lines changed

karma.conf.js

+16-13
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ const pkgList = {
224224
core: "@preact/signals-core",
225225
preact: "@preact/signals",
226226
react: "@preact/signals-react",
227+
"react/utils": "@preact/signals-react/utils",
227228
"react/auto": "@preact/signals-react/auto",
228229
"react/runtime": "@preact/signals-react/runtime",
229230
"react-transform": "@preact/signals-react-transform",
@@ -304,19 +305,21 @@ module.exports = function (config) {
304305
customLaunchers: localLaunchers,
305306

306307
files: [
307-
...filteredPkgList.some(i => /^react/.test(i)) ? [
308-
{
309-
// Provide some NodeJS globals to run babel in a browser environment
310-
pattern: "test/browser/nodeGlobals.js",
311-
watched: false,
312-
type: "js",
313-
},
314-
{
315-
pattern: "test/browser/babel.js",
316-
watched: false,
317-
type: "js",
318-
},
319-
] : [],
308+
...(filteredPkgList.some(i => /^react/.test(i))
309+
? [
310+
{
311+
// Provide some NodeJS globals to run babel in a browser environment
312+
pattern: "test/browser/nodeGlobals.js",
313+
watched: false,
314+
type: "js",
315+
},
316+
{
317+
pattern: "test/browser/babel.js",
318+
watched: false,
319+
type: "js",
320+
},
321+
]
322+
: []),
320323
{
321324
pattern:
322325
process.env.TESTS ||

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
"private": true,
44
"scripts": {
55
"prebuild": "shx rm -rf packages/*/dist/",
6-
"build": "pnpm build:core && pnpm build:preact && pnpm build:react-runtime && pnpm build:react-auto && pnpm build:react && pnpm build:react-transform",
6+
"build": "pnpm build:core && pnpm build:preact && pnpm build:react-runtime && pnpm build:react-auto && pnpm build:react && pnpm build:react-transform && pnpm build:react-utils",
77
"_build": "microbundle --raw --globals @preact/signals-core=preactSignalsCore,preact/hooks=preactHooks,@preact/signals-react/runtime=reactSignalsRuntime",
88
"build:core": "pnpm _build --cwd packages/core && pnpm postbuild:core",
99
"build:preact": "pnpm _build --cwd packages/preact && pnpm postbuild:preact",
1010
"build:react": "pnpm _build --cwd packages/react --external \"react,@preact/signals-react/runtime,@preact/signals-core\" && pnpm postbuild:react",
11+
"build:react-utils": "pnpm _build --cwd packages/react/utils && pnpm postbuild:react-utils",
1112
"build:react-auto": "pnpm _build --cwd packages/react/auto && pnpm postbuild:react-auto",
1213
"build:react-runtime": "pnpm _build --cwd packages/react/runtime && pnpm postbuild:react-runtime",
1314
"build:react-transform": "pnpm _build --no-compress --cwd packages/react-transform",
1415
"postbuild:core": "cd packages/core/dist && shx mv -f index.d.ts signals-core.d.ts",
1516
"postbuild:preact": "cd packages/preact/dist && shx mv -f preact/src/index.d.ts signals.d.ts && shx rm -rf preact",
1617
"postbuild:react": "cd packages/react/dist && shx mv -f react/src/index.d.ts signals.d.ts && shx rm -rf react",
18+
"postbuild:react-utils": "cd packages/react/utils/dist && shx mv -f react/utils/src/index.d.ts . && shx rm -rf react",
1719
"postbuild:react-auto": "cd packages/react/auto/dist && shx mv -f react/auto/src/*.d.ts . && shx rm -rf react",
1820
"postbuild:react-runtime": "cd packages/react/runtime/dist && shx mv -f react/runtime/src/*.d.ts . && shx rm -rf react",
1921
"lint": "pnpm lint:eslint && pnpm lint:tsc",

packages/react/package.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@
4444
"import": "./auto/dist/auto.mjs",
4545
"require": "./auto/dist/auto.js"
4646
},
47-
"./auto/package.json": "./auto/package.json"
47+
"./auto/package.json": "./auto/package.json",
48+
"./utils": {
49+
"types": "./utils/dist/index.d.ts",
50+
"browser": "./utils/dist/utils.module.js",
51+
"import": "./utils/dist/utils.mjs",
52+
"require": "./utils/dist/utils.js"
53+
},
54+
"./utils/package.json": "./utils/package.json"
4855
},
4956
"mangle": "../../mangle.json",
5057
"files": [

packages/react/utils/package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@preact/signals-react-runtime",
3+
"description": "Sub package for @preact/signals-react that contains some useful utilities",
4+
"private": true,
5+
"amdName": "reactSignalsutils",
6+
"main": "dist/utils.js",
7+
"module": "dist/utils.module.js",
8+
"unpkg": "dist/utils.min.js",
9+
"types": "dist/index.d.ts",
10+
"source": "src/index.ts",
11+
"mangle": "../../../mangle.json",
12+
"exports": {
13+
".": {
14+
"types": "./dist/index.d.ts",
15+
"browser": "./dist/utils.module.js",
16+
"import": "./dist/utils.mjs",
17+
"require": "./dist/utils.js"
18+
}
19+
},
20+
"dependencies": {
21+
"@preact/signals-core": "workspace:^1.3.0"
22+
},
23+
"peerDependencies": {
24+
"react": "^16.14.0 || 17.x || 18.x || 19.x"
25+
}
26+
}

packages/react/utils/src/index.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ReadonlySignal, Signal } from "@preact/signals-core";
2+
import { useSignal } from "@preact/signals-react";
3+
import { useSignals } from "@preact/signals-react/runtime";
4+
import { Fragment, createElement, useMemo } from "react";
5+
6+
interface ShowProps<T = boolean> {
7+
when: Signal<T> | ReadonlySignal<T>;
8+
fallback?: JSX.Element;
9+
children: JSX.Element | ((value: T) => JSX.Element);
10+
}
11+
12+
export function Show<T = boolean>(props: ShowProps<T>): JSX.Element | null {
13+
useSignals();
14+
const value = props.when.value;
15+
if (!value) return props.fallback || null;
16+
return typeof props.children === "function"
17+
? props.children(value)
18+
: props.children;
19+
}
20+
21+
interface ForProps<T> {
22+
each: Signal<Array<T>> | ReadonlySignal<Array<T>>;
23+
fallback?: JSX.Element;
24+
children: (value: T, index: number) => JSX.Element;
25+
}
26+
27+
export function For<T>(props: ForProps<T>): JSX.Element | null {
28+
useSignals();
29+
const cache = useMemo(() => new Map(), []);
30+
const list = props.each.value;
31+
if (!list.length) return props.fallback || null;
32+
const items = list.map((value, key) => {
33+
if (!cache.has(value)) {
34+
cache.set(value, props.children(value, key));
35+
}
36+
return cache.get(value);
37+
});
38+
return createElement(Fragment, { children: items });
39+
}
40+
41+
export function useLiveSignal<T>(value: Signal<T> | ReadonlySignal<T>) {
42+
const s = useSignal(value);
43+
if (s.peek() !== value) s.value = value;
44+
return s;
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { For, Show } from "../../src";
2+
import {
3+
act,
4+
checkHangingAct,
5+
createRoot,
6+
Root,
7+
} from "../../../test/shared/utils";
8+
import { signal } from "@preact/signals-react";
9+
import { createElement } from "react";
10+
11+
describe("@preact/signals-react-utils", () => {
12+
let scratch: HTMLDivElement;
13+
let root: Root;
14+
async function render(element: Parameters<Root["render"]>[0]) {
15+
await act(() => root.render(element));
16+
}
17+
18+
beforeEach(async () => {
19+
scratch = document.createElement("div");
20+
document.body.appendChild(scratch);
21+
root = await createRoot(scratch);
22+
});
23+
24+
afterEach(async () => {
25+
checkHangingAct();
26+
await act(() => root.unmount());
27+
scratch.remove();
28+
});
29+
30+
describe("<Show />", () => {
31+
it("Should reactively show an element", () => {
32+
const toggle = signal(false)!;
33+
const Paragraph = (p: any) => <p>{p.children}</p>;
34+
act(() => {
35+
render(
36+
<Show when={toggle} fallback={<Paragraph>Hiding</Paragraph>}>
37+
<Paragraph>Showing</Paragraph>
38+
</Show>
39+
);
40+
});
41+
expect(scratch.innerHTML).to.eq("<p>Hiding</p>");
42+
43+
act(() => {
44+
toggle.value = true;
45+
});
46+
expect(scratch.innerHTML).to.eq("<p>Showing</p>");
47+
});
48+
});
49+
50+
describe("<For />", () => {
51+
it("Should iterate over a list of signals", () => {
52+
const list = signal<Array<string>>([])!;
53+
const Paragraph = (p: any) => <p>{p.children}</p>;
54+
act(() => {
55+
render(
56+
<For each={list} fallback={<Paragraph>No items</Paragraph>}>
57+
{item => <Paragraph key={item}>{item}</Paragraph>}
58+
</For>
59+
);
60+
});
61+
expect(scratch.innerHTML).to.eq("<p>No items</p>");
62+
63+
act(() => {
64+
list.value = ["foo", "bar"];
65+
});
66+
expect(scratch.innerHTML).to.eq("<p>foo</p><p>bar</p>");
67+
});
68+
});
69+
});

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@preact/signals-core": ["./packages/core/src/index.ts"],
1616
"@preact/signals": ["./packages/preact/src/index.ts"],
1717
"@preact/signals-react": ["./packages/react/src/index.ts"],
18+
"@preact/signals-react/utils": ["./packages/react/utils/src/index.ts"],
1819
"@preact/signals-react/runtime": [
1920
"./packages/react/runtime/src/index.ts"
2021
],

0 commit comments

Comments
 (0)