Skip to content

Commit 2fe4daf

Browse files
committed
feat: add preact-10 app demo
1 parent 2cf65ab commit 2fe4daf

File tree

5 files changed

+158
-1
lines changed

5 files changed

+158
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ steps.
66
* [Angular 17](./angular-17/)
77
* [Angular 21](./angular-21/)
88
* [Hono 4](./hono-4/)
9+
* [Preact 10](./preact-10/)
910
* [Solid 1](./solid-1/)

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"workspace": ["./angular-*", "./hono-*", "./solid-*"],
2+
"workspace": ["./angular-*", "./hono-*", "./preact-*", "./solid-*"],
33
"imports": {
44
"jsonplaceholder-types/": "https://esm.sh/*jsonplaceholder-types@1.0.2&dev/"
55
},

preact-10/deno.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"imports": {
3+
"@preact/signals": "https://esm.sh/*@preact/signals@2.4.0?dev",
4+
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.12.1?dev",
5+
"preact": "https://esm.sh/*preact@10.27.2?dev",
6+
"preact/": "https://esm.sh/*preact@10.27.2&dev/"
7+
}
8+
}

preact-10/index.html

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Buildless Preact 10 app</title>
6+
<script type="importmap">
7+
{
8+
"imports": {
9+
"@preact/signals": "https://esm.sh/*@preact/signals@2.4.0?dev",
10+
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.12.1?dev",
11+
"preact": "https://esm.sh/*preact@10.27.2?dev",
12+
"preact/": "https://esm.sh/*preact@10.27.2&dev/"
13+
}
14+
}
15+
</script>
16+
<script type="module">
17+
import {
18+
availablePresets,
19+
registerPreset,
20+
} from "https://esm.sh/@babel/standalone@7.28.1";
21+
22+
registerPreset("jsx", {
23+
presets: [[availablePresets["react"], { runtime: "automatic" }]],
24+
});
25+
</script>
26+
<!-- `data-plugins=""` attribute is required for @babel/standalone@7.28.1 -->
27+
<script
28+
type="text/babel"
29+
data-presets="jsx,typescript"
30+
data-plugins=""
31+
data-type="module"
32+
src="./main.tsx"
33+
></script>
34+
</head>
35+
<body></body>
36+
</html>

preact-10/main.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/** @jsxImportSource preact */
2+
3+
import {
4+
type ReadonlySignal,
5+
useSignal,
6+
useSignalEffect,
7+
} from "@preact/signals";
8+
import { render } from "preact";
9+
import type { User } from "jsonplaceholder-types/types/user";
10+
import type { Post } from "jsonplaceholder-types/types/post";
11+
12+
const urlBase = "https://jsonplaceholder.typicode.com";
13+
14+
function useUsers() {
15+
const users = useSignal<User[] | undefined>(undefined);
16+
const loading = useSignal(false);
17+
18+
useSignalEffect(function fetchUsers() {
19+
const ctrl = new AbortController();
20+
const signal = ctrl.signal;
21+
22+
loading.value = true;
23+
fetch(`${urlBase}/users`, { signal })
24+
.then(async (response) => {
25+
const data = await response.json();
26+
if (signal.aborted) return;
27+
users.value = data;
28+
})
29+
.catch((error) => {
30+
if (!(error instanceof DOMException && error.name === "AbortError")) {
31+
throw error;
32+
}
33+
})
34+
.finally(() => loading.value = false);
35+
36+
return () => ctrl.abort();
37+
});
38+
39+
return [users, { loading }] as const;
40+
}
41+
42+
function usePosts(userId: ReadonlySignal<number | undefined>) {
43+
const posts = useSignal<Post[] | undefined>(undefined);
44+
const loading = useSignal(false);
45+
46+
useSignalEffect(function fetchPosts() {
47+
if (userId.value === undefined) return;
48+
49+
const ctrl = new AbortController();
50+
const signal = ctrl.signal;
51+
loading.value = true;
52+
fetch(`${urlBase}/posts?userId=${userId.value}`, { signal })
53+
.then(async (response) => {
54+
const data = await response.json();
55+
if (signal.aborted) return;
56+
posts.value = data;
57+
})
58+
.catch((error) => {
59+
if (!(error instanceof DOMException && error.name === "AbortError")) {
60+
throw error;
61+
}
62+
})
63+
.finally(() => loading.value = false);
64+
65+
return () => ctrl.abort();
66+
});
67+
68+
return [posts, { loading }] as const;
69+
}
70+
71+
function App() {
72+
const selectedUserId = useSignal<number | undefined>(undefined);
73+
const [users, { loading: loadingUsers }] = useUsers();
74+
const [posts, { loading: loadingPosts }] = usePosts(selectedUserId);
75+
76+
return (
77+
<>
78+
<h1>Buildless Preact 10 app</h1>
79+
{users.value !== undefined && (
80+
<label>
81+
Select User:
82+
<select
83+
onChange={(event) =>
84+
selectedUserId.value = +event.currentTarget.value}
85+
>
86+
<option hidden selected></option>
87+
{users.value.map((user) => (
88+
<option key={user.id} value={user.id}>
89+
@{user.username}: {user.name}
90+
</option>
91+
))}
92+
</select>
93+
</label>
94+
) || loadingUsers.value && <p>Loading Users...</p>}
95+
{posts.value !== undefined && (
96+
<ul>
97+
{posts.value.map((post) => <li key={post.id}>{post.title}</li>)}
98+
</ul>
99+
) ||
100+
loadingPosts.value && <p>Loading Posts...</p> ||
101+
users.value !== undefined && <p>Select User to view posts</p>}
102+
<p>
103+
Data Source:
104+
<a href="https://jsonplaceholder.typicode.com/" target="_blank">
105+
JSONPlaceholder
106+
</a>
107+
</p>
108+
</>
109+
);
110+
}
111+
112+
render(<App />, document.body);

0 commit comments

Comments
 (0)