Skip to content

Commit 94fafbd

Browse files
committed
feat: add a hono-4 app demo
1 parent d9c9735 commit 94fafbd

File tree

5 files changed

+153
-1
lines changed

5 files changed

+153
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ steps.
55

66
* [Angular 17](./angular-17/)
77
* [Angular 21](./angular-21/)
8+
* [Hono 4](./hono-4/)

deno.json

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

hono-4/deno.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"imports": {
3+
"hono/": "https://esm.sh/*hono@4.10.4&dev/"
4+
}
5+
}

hono-4/index.html

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

hono-4/main.tsx

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

0 commit comments

Comments
 (0)