Persist All Boards with localStorage
In the previous section, we created boardsReducer and a pair of storage helpers. Now let’s wire them into the board detail page and the home page so that kudos persist across navigation and page refreshes.
Update the board detail page
Replace the contents of src/routes/boards.$boardId.tsx with:
import { createFileRoute } from "@tanstack/react-router";
import type { Kudo } from "@/data/types";
import { useEffect, useReducer } from "react";
import AddKudoForm from "@/components/add-kudo-form";
import KudoCard from "@/components/kudo-card";
import { boardsReducer } from "@/reducers/boards";
import { loadBoards, saveBoards } from "@/data/board-storage";
export const Route = createFileRoute("/boards/$boardId")({
component: BoardPage,
});
function BoardPage() {
const { boardId } = Route.useParams();
const [boards, dispatch] = useReducer(boardsReducer, null, loadBoards);
useEffect(() => {
saveBoards(boards);
}, [boards]);
const board = boards.find((b) => b.id === boardId);
if (!board) {
return (
<div className="text-center">
<h1 className="mb-2 text-2xl font-bold">Board not found</h1>
<p className="text-muted-foreground">
The board you're looking for doesn't exist.
</p>
</div>
);
}
const handleAddKudo = (kudo: Kudo) => {
dispatch({ type: "kudo_added", boardId: board.id, kudo });
};
const handleDeleteKudo = (kudoId: string) => {
dispatch({ type: "kudo_deleted", boardId: board.id, kudoId });
};
return (
<div>
<h1 className="mb-2 text-3xl font-bold">{board.title}</h1>
<p className="mb-6 text-muted-foreground">{board.description}</p>
<AddKudoForm onAdd={handleAddKudo} />
{board.kudos.length === 0 ? (
<p className="text-muted-foreground">
No kudos yet. Be the first to add one!
</p>
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{board.kudos.map((kudo) => (
<KudoCard
key={kudo.id}
kudo={kudo}
onDelete={() => handleDeleteKudo(kudo.id)}
/>
))}
</div>
)}
</div>
);
}
Three things to notice:
-
useReducer(boardsReducer, null, loadBoards). The third argument is a lazy initializer. React callsloadBoards(null)once on mount to compute the initial state. Thenullhere is just the input we pass to the initializer. It is not the state value the reducer works with. -
useEffect(() => saveBoards(boards), [boards]). Whenever the boards state changes, we write it to localStorage. -
Actions include
boardIdso the reducer knows which board to update.
Update the home page
Replace the contents of src/routes/index.tsx with:
import { useState } from "react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { loadBoards } from "@/data/board-storage";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export const Route = createFileRoute("/")({
component: HomePage,
});
function HomePage() {
const [boards] = useState(loadBoards);
return (
<div>
<h1 className="mb-6 text-3xl font-bold">Boards</h1>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{boards.map((board) => (
<Link
key={board.id}
to="/boards/$boardId"
params={{ boardId: board.id }}
>
<Card className="transition-shadow hover:shadow-md">
<CardHeader>
<CardTitle>{board.title}</CardTitle>
<CardDescription>{board.description}</CardDescription>
<p className="text-sm text-muted-foreground">
{board.kudos.length}{" "}
{board.kudos.length === 1 ? "kudo" : "kudos"}
</p>
</CardHeader>
</Card>
</Link>
))}
</div>
</div>
);
}
The home page uses useState(loadBoards) to read the latest boards from localStorage on mount. Navigating between routes mounts and unmounts components, so the home page reads localStorage again every time you come back from a board page.
Test it
Try this sequence:
- Navigate to “Happy Birthday, Sam!” and add a kudo
- Click “Boards” to go back to the home page
- The kudo count for Sam’s board should now show “1 kudo”
- Refresh the page, and the kudo is still there
Checkpoint: Commit your progress.
git add .
git commit -m "kudoboard-11: Persist all boards with localStorage"
git push