Most of MovieDB is server-rendered and enhanced with Unpoly, and that's our default. Sometimes one region of a page needs more interactivity than that model gives you comfortably. Then we build an island: a React component mounted into a server-rendered page. This card teaches the React basics you need for that — components, state, effects, talking to Rails — and how to mount an island from an Unpoly compiler. It is not about building single-page apps.
Important
Work on this lesson in
advisormode.
Learning goals
- You can explain what a React island is and how it differs from a full single-page app.
- You can write a React component with JSX, hold its state with
useState, and render it from data the server passed in. - You can use
useEffectto load data, react to changes and run periodic work, and you can explain why effects need cleanup. - You can let an island talk to Rails through a small JSON API, both reading and writing.
- You can mount and unmount a React component from an Unpoly compiler so that fragment updates don't leak.
- You can set up JSX compilation in an esbuild-based Rails app.
Setup
Do this yourself; it's part of the lesson.
yarn add react react-dom- Write components and the compiler in
.jsxfiles. Alternatively add'.js': 'jsx'to theloadermap inesbuild.config.js, so every JavaScript file may contain JSX. - Add
jsx: 'automatic'to the esbuild options, so you don't needimport React from 'react'in every file.
That's all — esbuild compiles JSX natively; no Babel involved.
Mounting an island
The server renders a plain element with the data the island needs. An Unpoly compiler mounts the component and returns a destructor that unmounts it when the fragment goes away:
<div class="chat" data-room="3012"></div>
import ReactDOM from 'react-dom/client'
import ChatSession from './chat_session'
up.compiler('.chat', function(element, { room }) {
const session = new ChatSession(room)
const root = ReactDOM.createRoot(element)
root.render(<Chat session={session} />)
return () => root.unmount()
})
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
- 📄 Learn React Show archive.org snapshot — the official tutorial; concentrate on components, props, state and effects
- 📄 JSON APIs: default design for common features
- 📄 Best practices for REST API design
- 📄 How to (and how not to) design REST APIs Show archive.org snapshot
- 📄
JSON in Ruby on Rails:
render json:, API mode, Jbuilder & serializers Show archive.org snapshot — the short tutorial for building a small JSON API inside an existing Rails app:render json:with status codes,only:/except:/methods:, JSON request bodies inparams, error handling withrescue_from. Read the first half; skip API mode and the serializer gems. - 📄 Rails Guides: Rendering JSON Show archive.org snapshot and JSON parameters Show archive.org snapshot — the two reference sections behind it.
Exercises
1. Port the star rating to an island
In Agentic coding basics you built a 1-to-5-star rating with an average, server-rendered. Port it to a React island on the movie page: same feature, new architecture. When it works, compare: what got simpler, what got more complex?
Keep the state as a fixed set of slots — the hovered value, the user's own rating, the average, the count, whether a save is in progress, an error — no lists. Clicking a star saves the rating through a JSON endpoint; the response updates average and count.
2. Port the reviews to a live discussion island
Also in Agentic coding basics you built review texts for movies. Turn that list into a live discussion island: it loads the movie's reviews when it mounts and shows a loading state meanwhile, renders them as a flat list, lets the user post a new comment from a textarea, and polls for new comments every few seconds.
Clean up the polling interval when the component unmounts — this is what the compiler's destructor is for. No replies or threads.