Chapter 4 of 7

Add interactivity

Add a star rating to each book. It's an island: the one part of the page that ships JavaScript.

On this page

Write an island

Any component in a file ending in .island.tsx is an island. It renders to HTML on the server like everything else, then hydrates in the browser so it can respond to clicks.

import { useState } from "react";

export const hydrate = "visible";

export default function Rating({ title }: { title: string }) {
  const [stars, setStars] = useState(0);
  return (
    <div className="mt-8" role="group" aria-label={`Rate ${title}`}>
      {[1, 2, 3, 4, 5].map((n) => (
        <button key={n} type="button" aria-label={`${n} of 5`} aria-pressed={n <= stars} onClick={() => setStars(n)} className={n <= stars ? "text-amber-500" : "text-gray-300"}>
          ★
        </button>
      ))}
      <p className="text-sm text-gray-500">{stars ? `You rated it ${stars} of 5.` : "Not rated yet."}</p>
    </div>
  );
}

Use it on the book page

import Rating from "$components/Rating.island";

// ...inside BookPage, after the summary:
<p className="mt-6">{book.summary}</p>
<Rating title={book.title} />

Open a book and pick a rating. The rest of the page is the same static HTML as before.

What ships to the browser

  1. ServerWhole page to HTMLHeading, summary, the rating's first render
  2. BrowserShows the HTMLReadable before any JavaScript runs
  3. BrowserHydrates RatingOnly this component's code loads
One page, one island

View the page's source and you'll find the rating wrapped in <optz-island data-id="app/components/Rating.island.tsx">. That element, and nothing else on the page, is handed to React in the browser. A page with no islands ships no React at all.

Choose when it wakes up

hydratewhen the island's JavaScript runs
"load" (default)As soon as the page loads
"idle"When the browser has a free moment
"visible"When the island scrolls into view

Important

Island props travel inside the HTML as JSON. Pass strings, numbers, arrays and plain objects, not functions or class instances.

To make islands even smaller, set client.runtime: "preact" in optz.config.ts: the same React code hydrates with Preact, about 4 KB instead of about 60 KB.

Next, take input from your visitors.