Playground
The real playground is a project on your own machine. The scratchpad underneath runs in this page, compiles your JSX in your browser, and was checked to print exactly what Node printed for the same code when this page was built.
A scratchpad that runs here
Press Edit, change the code, and press Run here. It has the same
check() the lessons use, and the rendered result appears in the framed region
between the code and the output. Everything is compiled and run in this page: the JSX by the
TypeScript compiler that ships with the course, the components by the React bundle in
assets/js. Nothing is sent anywhere.
App.jsx
import { useState, useEffect, act } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
const [n, setN] = useState(0);
useEffect(() => {
console.log('effect ' + n);
}, [n]);
return <button onClick={() => setN(n + 1)}>clicked {n}</button>;
}
const root = createRoot(stage);
await act(async () => root.render(<Counter />));
console.log(stage.innerHTML);
const button = stage.querySelector('button');
await act(async () => button.click());
console.log(stage.innerHTML);
check('one click, one more', button.textContent, 'clicked 1');
await act(async () => root.unmount());
What node printed (React 19.2.7, development build), when the build ran it
node App.jsx
effect 0 <button>clicked 0</button> effect 1 <button>clicked 1</button> ok one click, one more
What stops a runaway panel
Edited code can be wrong in ways that never finish, so the page has two guards. A
while or for loop that spins more than two million times is stopped
with a RangeError that says so. And a run that is still rendering after five
seconds is ended: every root it created is unmounted, and the output says
Stopped after 5 seconds: something kept rendering. At most 1,000 lines, and at most
50 console warnings, are kept, so code that logs on every render cannot flood the page.
One kind of loop is not guarded. An endless chain of promise callbacks, such as
(async function f() { await 0; f(); })(), runs in the microtask queue, which
runs before the browser gets to the five-second timer, so the timer never fires and the tab
stays busy until the browser offers to stop the page. Making every await wait for
a timer would fix that and would change the ordering that lesson 26 teaches, so it is not done.
The real playground is a project
On your own machine React lives in a project with a bundler. Vite is the usual choice, and it needs the npm registry, which this course never talks to, so this is shown and not run.
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm run dev
needs Vite and the npm registry, which this offline course never contacts. The scratchpad above is the playground that runs here.