A tool for people curious about the React Server Components protocol rscexplorer.dev/
rsc react

rm example

+56 -38
-37
src/client/samples.ts
··· 576 576 return String(v) 577 577 }`, 578 578 }, 579 - actionerror: { 580 - name: "Action Error", 581 - server: `import { Button } from './client' 582 - 583 - export default function App() { 584 - return ( 585 - <div> 586 - <h1>Action Error</h1> 587 - <Button failAction={failAction} /> 588 - </div> 589 - ) 590 - } 591 - 592 - async function failAction() { 593 - 'use server' 594 - throw new Error('Action failed intentionally') 595 - }`, 596 - client: `'use client' 597 - 598 - import { useTransition } from 'react' 599 - 600 - export function Button({ failAction }) { 601 - const [isPending, startTransition] = useTransition() 602 - 603 - const handleClick = () => { 604 - startTransition(async () => { 605 - await failAction() 606 - }) 607 - } 608 - 609 - return ( 610 - <button onClick={handleClick} disabled={isPending}> 611 - {isPending ? 'Running...' : 'Trigger Failing Action'} 612 - </button> 613 - ) 614 - }`, 615 - }, 616 579 cve: { 617 580 name: "CVE-2025-55182", 618 581 server: `import { Instructions } from './client'
+37 -1
tests/actionerror.spec.ts
··· 2 2 import { createHelpers, launchBrowser, type TestHelpers } from "./helpers.ts"; 3 3 import type { Browser, Page } from "playwright"; 4 4 5 + const ACTION_ERROR_SERVER = `import { Button } from './client' 6 + 7 + export default function App() { 8 + return ( 9 + <div> 10 + <h1>Action Error</h1> 11 + <Button failAction={failAction} /> 12 + </div> 13 + ) 14 + } 15 + 16 + async function failAction() { 17 + 'use server' 18 + throw new Error('Action failed intentionally') 19 + }`; 20 + 21 + const ACTION_ERROR_CLIENT = `'use client' 22 + 23 + import { useTransition } from 'react' 24 + 25 + export function Button({ failAction }) { 26 + const [isPending, startTransition] = useTransition() 27 + 28 + const handleClick = () => { 29 + startTransition(async () => { 30 + await failAction() 31 + }) 32 + } 33 + 34 + return ( 35 + <button onClick={handleClick} disabled={isPending}> 36 + {isPending ? 'Running...' : 'Trigger Failing Action'} 37 + </button> 38 + ) 39 + }`; 40 + 5 41 let browser: Browser; 6 42 let page: Page; 7 43 let h: TestHelpers; ··· 21 57 }); 22 58 23 59 test("action error - throwing action shows error in entry and clears pending state", async () => { 24 - await h.load("actionerror"); 60 + await h.loadCode(ACTION_ERROR_SERVER, ACTION_ERROR_CLIENT); 25 61 26 62 // Render completes 27 63 expect(await h.stepAll()).toMatchInlineSnapshot(`
+19
tests/helpers.ts
··· 17 17 18 18 export type TestHelpers = { 19 19 load: (sample: string) => Promise<void>; 20 + loadCode: (server: string, client: string) => Promise<void>; 20 21 step: () => Promise<string | null>; 21 22 stepAll: () => Promise<string | null>; 22 23 stepInfo: () => Promise<string>; ··· 40 41 41 42 async function load(sample: string): Promise<void> { 42 43 await page.goto(`http://localhost:5599/?s=${sample}`); 44 + // Wait for iframe to load and get frame reference 45 + const iframe = page.frameLocator("iframe"); 46 + frameRef = iframe; 47 + // Wait for content inside iframe 48 + await iframe.getByTestId("flight-entry").first().waitFor({ timeout: 10000 }); 49 + await page.waitForTimeout(100); 50 + prevRowTexts = []; 51 + prevStatuses = []; 52 + prevPreview = await getPreviewText(); 53 + previewAsserted = true; 54 + } 55 + 56 + async function loadCode(server: string, client: string): Promise<void> { 57 + const json = JSON.stringify({ server, client }); 58 + // btoa with UTF-8 encoding 59 + const encoded = btoa(unescape(encodeURIComponent(json))); 60 + await page.goto(`http://localhost:5599/?c=${encodeURIComponent(encoded)}`); 43 61 // Wait for iframe to load and get frame reference 44 62 const iframe = page.frameLocator("iframe"); 45 63 frameRef = iframe; ··· 290 308 291 309 return { 292 310 load, 311 + loadCode, 293 312 step, 294 313 stepAll, 295 314 stepInfo,