astrobench · play with code
Step
8/10

Search the logs

Type. Find. Pop. The whole loop.

Time to tie it together . Your content collection lives at build time. Your API endpoint exposes it as JSON. Your island gives users a way to search it.

 

The island fetches your endpoint on mount, holds the logs in state, and filters them as the user types.

src/components/Search.tsx — the search island read-only
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useEffect, useState } from "preact/hooks";

type Log = { id: string; title: string; author: string };

export default function Search() {
  const [logs, setLogs] = useState<Log[]>([]);
  const [q, setQ] = useState("");

  // fetch once, on mount
  useEffect(() => {
    fetch("/api/logs.json")
      .then((r) => r.json())
      .then(setLogs);
  }, []);

  const filtered = logs.filter((l) =>
    l.title.toLowerCase().includes(q.toLowerCase()));

  return (
    <div>
      <input  value="{q}"  onInput="{(e) => setQ(e.currentTarget.value)}"  placeholder="search…" />
      <ul>
        {filtered.map((l) => <li  key="{l.id}">{l.title}</li>)}
      </ul>
    </div>
  );
}

And drop it on the logs index — client:visible because it's not above the fold for everyone:

src/pages/logs/index.astro read-only
1
2
3
4
5
6
7
8
9
---
import Layout from "~/layouts/Layout.astro";
import Search from "~/components/Search.tsx";
---

<Layout title="Mission Log">
  <h1>Mission Log</h1>
  <Search  client:visible />
</Layout>

This little component is the whole client-side data story . Page is static HTML. Island hydrates when visible. Island fetches your own endpoint. Island re-renders on input.

 

For a thousand logs you'd swap filter() for a real search index. For ten, this is fine — and it's been fine for a million sites.

— page 8 —
saved a moment ago
progress 80%
earn +110 XP