Skip to content

Commit 78ed49b

Browse files
committed
refactor(example): split files
1 parent afd8b3a commit 78ed49b

File tree

23 files changed

+303
-261
lines changed

23 files changed

+303
-261
lines changed

packages/example/src/App.tsx

Lines changed: 8 additions & 261 deletions
Original file line numberDiff line numberDiff line change
@@ -1,271 +1,18 @@
1-
import { use, Suspense } from "react";
2-
import {
3-
Router,
4-
Outlet,
5-
useLocation,
6-
useSearchParams,
7-
useNavigate,
8-
route,
9-
} from "@funstack/router";
1+
import { Router, route } from "@funstack/router";
2+
import { Layout } from "./shared";
3+
import { homeRoute } from "./features/home";
4+
import { aboutRoute } from "./features/about";
5+
import { usersRoutes } from "./features/users";
6+
import { searchRoute } from "./features/search";
107

11-
// Types
12-
type User = { id: string; name: string; role: string };
13-
14-
// Sample user data (simulating a database)
15-
const users: User[] = [
16-
{ id: "1", name: "Alice", role: "Admin" },
17-
{ id: "2", name: "Bob", role: "User" },
18-
{ id: "3", name: "Charlie", role: "User" },
19-
];
20-
21-
// Simulated async data fetching (would be API calls in real app)
22-
async function fetchUsers(): Promise<User[]> {
23-
// Simulate network delay
24-
await new Promise((resolve) => setTimeout(resolve, 500));
25-
return users;
26-
}
27-
28-
async function fetchUser(id: string): Promise<User | null> {
29-
// Simulate network delay
30-
await new Promise((resolve) => setTimeout(resolve, 300));
31-
return users.find((u) => u.id === id) || null;
32-
}
33-
34-
// Layout component with navigation
35-
function Layout() {
36-
const location = useLocation();
37-
38-
return (
39-
<div>
40-
<nav>
41-
{/* Native <a> tags work for basic navigation thanks to Navigation API */}
42-
<a href="/">Home</a>
43-
<a href="/about">About</a>
44-
<a href="/users">Users</a>
45-
<a href="/search?q=react">Search</a>
46-
</nav>
47-
<main>
48-
<p style={{ color: "#666", fontSize: "0.9rem" }}>
49-
Current path: <code>{location.pathname}</code>
50-
{location.search && (
51-
<>
52-
{" "}
53-
| Search: <code>{location.search}</code>
54-
</>
55-
)}
56-
</p>
57-
<Outlet />
58-
</main>
59-
</div>
60-
);
61-
}
62-
63-
// Home page
64-
function Home() {
65-
return (
66-
<div>
67-
<h1>Welcome to FUNSTACK Router</h1>
68-
<p>This is a demo of the router based on the Navigation API.</p>
69-
<h2>Features demonstrated:</h2>
70-
<ul>
71-
<li>
72-
<a href="/about">Basic navigation</a> (native &lt;a&gt; tag)
73-
</li>
74-
<li>
75-
<a href="/users">Data loaders with Suspense</a> - async data fetching
76-
</li>
77-
<li>
78-
<a href="/users/1">Route parameters with loaders</a>
79-
</li>
80-
<li>
81-
<a href="/search?q=hello&page=1">Search parameters</a>
82-
</li>
83-
<li>
84-
See About page for <code>useNavigate()</code> hook demo
85-
</li>
86-
</ul>
87-
<h2>Data Loader Features:</h2>
88-
<ul>
89-
<li>
90-
Loaders execute before component renders (parallel with navigation)
91-
</li>
92-
<li>Results are cached - instant back/forward navigation</li>
93-
<li>Components receive loader result as a prop</li>
94-
<li>
95-
For async loaders, use React's <code>use()</code> hook with Suspense
96-
</li>
97-
</ul>
98-
</div>
99-
);
100-
}
101-
102-
// About page
103-
function About() {
104-
const navigate = useNavigate();
105-
106-
return (
107-
<div>
108-
<h1>About</h1>
109-
<p>
110-
FUNSTACK Router is a modern React router built on the Navigation API.
111-
</p>
112-
<button onClick={() => navigate("/")}>Go Home (programmatic)</button>
113-
</div>
114-
);
115-
}
116-
117-
// Users list page - receives Promise from loader, uses React's use() to suspend
118-
function Users({ data }: { data: Promise<User[]> }) {
119-
const userList = use(data);
120-
121-
return (
122-
<div>
123-
<h1>Users</h1>
124-
<p style={{ color: "#666", fontSize: "0.9rem" }}>
125-
(Data loaded via async loader with Suspense)
126-
</p>
127-
<div>
128-
{userList.map((user) => (
129-
<div key={user.id} className="user-card">
130-
<strong>{user.name}</strong> - {user.role}
131-
<br />
132-
<a href={`/users/${user.id}`}>View Profile</a>
133-
</div>
134-
))}
135-
</div>
136-
</div>
137-
);
138-
}
139-
140-
// User detail page - receives Promise from loader
141-
function UserDetail({ data }: { data: Promise<User | null> }) {
142-
const user = use(data);
143-
const navigate = useNavigate();
144-
145-
if (!user) {
146-
return (
147-
<div>
148-
<h1>User Not Found</h1>
149-
<p>The requested user does not exist.</p>
150-
<button onClick={() => navigate("/users")}>Back to Users</button>
151-
</div>
152-
);
153-
}
154-
155-
return (
156-
<div>
157-
<h1>{user.name}</h1>
158-
<p style={{ color: "#666", fontSize: "0.9rem" }}>
159-
(Data loaded via async loader with Suspense)
160-
</p>
161-
<p>
162-
<strong>ID:</strong> {user.id}
163-
</p>
164-
<p>
165-
<strong>Role:</strong> {user.role}
166-
</p>
167-
<button onClick={() => navigate("/users")}>Back to Users</button>
168-
</div>
169-
);
170-
}
171-
172-
// Search page demonstrating useSearchParams
173-
function Search() {
174-
const [searchParams, setSearchParams] = useSearchParams();
175-
const query = searchParams.get("q") || "";
176-
const page = searchParams.get("page") || "1";
177-
178-
return (
179-
<div>
180-
<h1>Search</h1>
181-
<p>
182-
<strong>Query:</strong> {query || "(none)"}
183-
</p>
184-
<p>
185-
<strong>Page:</strong> {page}
186-
</p>
187-
188-
<div style={{ marginTop: "1rem" }}>
189-
<input
190-
type="text"
191-
value={query}
192-
onChange={(e) =>
193-
setSearchParams((prev) => {
194-
prev.set("q", e.target.value);
195-
return prev;
196-
})
197-
}
198-
placeholder="Search..."
199-
style={{ padding: "0.5rem", marginRight: "0.5rem" }}
200-
/>
201-
<button
202-
onClick={() =>
203-
setSearchParams((prev) => {
204-
prev.set("page", String(Number(page) + 1));
205-
return prev;
206-
})
207-
}
208-
>
209-
Next Page
210-
</button>
211-
</div>
212-
</div>
213-
);
214-
}
215-
216-
// 404 page
217-
function NotFound() {
218-
const location = useLocation();
219-
220-
return (
221-
<div>
222-
<h1>404 - Not Found</h1>
223-
<p>
224-
The page <code>{location.pathname}</code> does not exist.
225-
</p>
226-
<a href="/">Go Home</a>
227-
</div>
228-
);
229-
}
230-
231-
// Route configuration using route() helper for type safety
2328
const routes = [
2339
route({
23410
path: "/",
23511
component: Layout,
236-
children: [
237-
route({ path: "", component: Home }),
238-
route({ path: "about", component: About }),
239-
// Async loader - component receives Promise and uses use() to suspend
240-
route({
241-
path: "users",
242-
component: Users,
243-
loader: () => fetchUsers(),
244-
}),
245-
// Async loader with params
246-
route({
247-
path: "users/:id",
248-
component: UserDetail,
249-
loader: ({ params }) => fetchUser(params.id),
250-
}),
251-
route({ path: "search", component: Search }),
252-
],
12+
children: [homeRoute, aboutRoute, ...usersRoutes, searchRoute],
25313
}),
25414
];
25515

256-
// Loading fallback for Suspense
257-
function LoadingSpinner() {
258-
return (
259-
<div style={{ padding: "2rem", textAlign: "center" }}>
260-
<p>Loading...</p>
261-
</div>
262-
);
263-
}
264-
26516
export function App() {
266-
return (
267-
<Suspense fallback={<LoadingSpinner />}>
268-
<Router routes={routes} />
269-
</Suspense>
270-
);
17+
return <Router routes={routes} />;
27118
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useNavigate } from "@funstack/router";
2+
3+
export function AboutPage() {
4+
const navigate = useNavigate();
5+
6+
return (
7+
<div>
8+
<h1>About</h1>
9+
<p>
10+
FUNSTACK Router is a modern React router built on the Navigation API.
11+
</p>
12+
<button onClick={() => navigate("/")}>Go Home (programmatic)</button>
13+
</div>
14+
);
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { AboutPage } from "./AboutPage";
2+
export { aboutRoute } from "./route";
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { route } from "@funstack/router";
2+
import { AboutPage } from "./AboutPage";
3+
4+
export const aboutRoute = route({ path: "about", component: AboutPage });
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export function HomePage() {
2+
return (
3+
<div>
4+
<h1>Welcome to FUNSTACK Router</h1>
5+
<p>This is a demo of the router based on the Navigation API.</p>
6+
<h2>Features demonstrated:</h2>
7+
<ul>
8+
<li>
9+
<a href="/about">Basic navigation</a> (native &lt;a&gt; tag)
10+
</li>
11+
<li>
12+
<a href="/users">Data loaders with Suspense</a> - async data fetching
13+
</li>
14+
<li>
15+
<a href="/users/1">Route parameters with loaders</a>
16+
</li>
17+
<li>
18+
<a href="/search?q=hello&page=1">Search parameters</a>
19+
</li>
20+
<li>
21+
See About page for <code>useNavigate()</code> hook demo
22+
</li>
23+
</ul>
24+
<h2>Data Loader Features:</h2>
25+
<ul>
26+
<li>
27+
Loaders execute before component renders (parallel with navigation)
28+
</li>
29+
<li>Results are cached - instant back/forward navigation</li>
30+
<li>Components receive loader result as a prop</li>
31+
<li>
32+
For async loaders, use React's <code>use()</code> hook with Suspense
33+
</li>
34+
</ul>
35+
</div>
36+
);
37+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { HomePage } from "./HomePage";
2+
export { homeRoute } from "./route";
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { route } from "@funstack/router";
2+
import { HomePage } from "./HomePage";
3+
4+
export const homeRoute = route({ path: "", component: HomePage });
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useSearchParams } from "@funstack/router";
2+
3+
export function SearchPage() {
4+
const [searchParams, setSearchParams] = useSearchParams();
5+
const query = searchParams.get("q") || "";
6+
const page = searchParams.get("page") || "1";
7+
8+
return (
9+
<div>
10+
<h1>Search</h1>
11+
<p>
12+
<strong>Query:</strong> {query || "(none)"}
13+
</p>
14+
<p>
15+
<strong>Page:</strong> {page}
16+
</p>
17+
18+
<div style={{ marginTop: "1rem" }}>
19+
<input
20+
type="text"
21+
value={query}
22+
onChange={(e) =>
23+
setSearchParams((prev) => {
24+
prev.set("q", e.target.value);
25+
return prev;
26+
})
27+
}
28+
placeholder="Search..."
29+
style={{ padding: "0.5rem", marginRight: "0.5rem" }}
30+
/>
31+
<button
32+
onClick={() =>
33+
setSearchParams((prev) => {
34+
prev.set("page", String(Number(page) + 1));
35+
return prev;
36+
})
37+
}
38+
>
39+
Next Page
40+
</button>
41+
</div>
42+
</div>
43+
);
44+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { SearchPage } from "./SearchPage";
2+
export { searchRoute } from "./route";

0 commit comments

Comments
 (0)