Taskboard 4. Async data
Use Request to load task data and Suspense to render pending, successful and rejected output for the asynchronous Tasks screen.
Table of contents
Stage 4. Load asynchronous task data
- Starting state
- Complete Stage 3 so the / route renders Taskboard with the shared store.
- Applied change
- Move the / route to TasksScreen, load /tasks.json once with request.get and pass the parsed tasks to store.replaceTasks.
- Observable result
- The route first shows Loading tasks and then the loaded Taskboard list. A rejected Request renders the Suspense error output.
- Next step
- Continue with Stage 5 and add a validated form to the loaded Tasks screen.
Observe Request through Suspense
Open Taskboard to see "Loading tasks..." followed by the parsed list. To observe rejection, change the Request URL to "/missing-tasks.json", rebuild and reload. Suspense renders its error branch for that non-OK Request.
- Request result
- request.get sets Accept: application/json by default, parses the response according to that header and rejects non-OK responses with an error that contains response and, when parsed, body.
- Suspense pending
- Suspense renders fallback while LoadedTasks and its Request are pending.
- Suspense resolved or rejected
- The resolved child replaces fallback. A rejection renders the error prop. suspenseKey identifies this host scope, so later renders with the same key reuse its resolved value or error.
public/tasks.json
json[
{ "id": 1, "title": "Learn Valyrian.js" },
{ "id": 2, "title": "Build Taskboard" }
]
src/router.tsx
tsximport { Router } from "valyrian.js/router";
import type { TaskboardStore } from "./state";
import { TasksScreen } from "./tasks-screen";
export function createRouter(store: TaskboardStore) {
const router = new Router();
router.add("/", () => (
<>
<button onclick={() => router.go("/about")}>About</button>
<TasksScreen store={store} />
</>
));
router.add("/about", () => (
<main>
<h1>About Taskboard</h1>
<button onclick={() => router.go("/")}>Tasks</button>
</main>
));
router.catch(404, () => <h1>Taskboard page not found</h1>);
return router;
}
src/tasks-screen.tsx
tsximport { request } from "valyrian.js/request";
import { Suspense } from "valyrian.js/suspense";
import { TaskboardApp } from "./app";
import type { Task, TaskboardStore } from "./state";
async function LoadedTasks({ store }: { store: TaskboardStore }) {
if (store.state.loaded === false) {
const tasks = (await request.get("/tasks.json")) as Task[];
store.replaceTasks(tasks);
}
return <TaskboardApp store={store} />;
}
export function TasksScreen({ store }: { store: TaskboardStore }) {
if (store.state.loaded) {
return <TaskboardApp store={store} />;
}
return (
<Suspense
suspenseKey="taskboard:tasks"
fallback={<p>Loading tasks...</p>}
error={(error) => <p>{String(error)}</p>}
>
<LoadedTasks store={store} />
</Suspense>
);
}