A tool for people curious about the React Server Components protocol
at main 4.1 kB view raw
1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>RSC Explorer Embeds</title> 7 <style> 8 body { 9 font-family: system-ui, sans-serif; 10 max-width: 900px; 11 margin: 0 auto; 12 padding: 20px; 13 line-height: 1.6; 14 } 15 h2 { 16 margin-top: 40px; 17 border-bottom: 1px solid #eee; 18 padding-bottom: 8px; 19 } 20 iframe { 21 width: 100%; 22 height: 500px; 23 border: none; 24 border-radius: 8px; 25 margin-bottom: 40px; 26 } 27 </style> 28 </head> 29 <body> 30 <h1>RSC Explorer Embeds</h1> 31 <p>Multiple embedded RSC explorers demonstrating different React Server Components patterns.</p> 32 33 <h2>Hello World</h2> 34 <p>The simplest possible server component.</p> 35 <iframe id="hello"></iframe> 36 37 <h2>Counter</h2> 38 <p>A client component with state, rendered from a server component.</p> 39 <iframe id="counter"></iframe> 40 41 <h2>Async Component</h2> 42 <p>Server components can be async and use Suspense for loading states.</p> 43 <iframe id="async"></iframe> 44 45 <h2>Form Action</h2> 46 <p>Server actions handle form submissions with useActionState.</p> 47 <iframe id="form"></iframe> 48 49 <script> 50 function encodeCode(code) { 51 const json = JSON.stringify(code); 52 return encodeURIComponent(btoa(unescape(encodeURIComponent(json)))); 53 } 54 55 function setEmbed(id, code) { 56 document.getElementById(id).src = `/embed.html?c=${encodeCode(code)}`; 57 } 58 59 setEmbed("hello", { 60 server: `export default function App() { 61 return <h1>Hello World</h1> 62}`, 63 client: `'use client'`, 64 }); 65 66 setEmbed("counter", { 67 server: `import { Counter } from './client' 68 69export default function App() { 70 return ( 71 <div> 72 <h1>Counter</h1> 73 <Counter initialCount={0} /> 74 </div> 75 ) 76}`, 77 client: `'use client' 78 79import { useState } from 'react' 80 81export function Counter({ initialCount }) { 82 const [count, setCount] = useState(initialCount) 83 84 return ( 85 <div> 86 <p>Count: {count}</p> 87 <div style={{ display: 'flex', gap: 8 }}> 88 <button onClick={() => setCount(c => c - 1)}>−</button> 89 <button onClick={() => setCount(c => c + 1)}>+</button> 90 </div> 91 </div> 92 ) 93}`, 94 }); 95 96 setEmbed("async", { 97 server: `import { Suspense } from 'react' 98 99export default function App() { 100 return ( 101 <div> 102 <h1>Async Component</h1> 103 <Suspense fallback={<p>Loading...</p>}> 104 <SlowComponent /> 105 </Suspense> 106 </div> 107 ) 108} 109 110async function SlowComponent() { 111 await new Promise(r => setTimeout(r, 500)) 112 return <p>Data loaded!</p> 113}`, 114 client: `'use client'`, 115 }); 116 117 setEmbed("form", { 118 server: `import { Form } from './client' 119 120export default function App() { 121 return ( 122 <div> 123 <h1>Form Action</h1> 124 <Form greetAction={greet} /> 125 </div> 126 ) 127} 128 129async function greet(prevState, formData) { 130 'use server' 131 await new Promise(r => setTimeout(r, 500)) 132 const name = formData.get('name') 133 if (!name) return { message: null, error: 'Please enter a name' } 134 return { message: \`Hello, \${name}!\`, error: null } 135}`, 136 client: `'use client' 137 138import { useActionState } from 'react' 139 140export function Form({ greetAction }) { 141 const [state, formAction, isPending] = useActionState(greetAction, { 142 message: null, 143 error: null 144 }) 145 146 return ( 147 <form action={formAction}> 148 <div style={{ display: 'flex', gap: 8 }}> 149 <input 150 name="name" 151 placeholder="Enter your name" 152 style={{ padding: '8px 12px', borderRadius: 4, border: '1px solid #ccc' }} 153 /> 154 <button disabled={isPending}> 155 {isPending ? 'Sending...' : 'Greet'} 156 </button> 157 </div> 158 {state.error && <p style={{ color: 'red', marginTop: 8 }}>{state.error}</p>} 159 {state.message && <p style={{ color: 'green', marginTop: 8 }}>{state.message}</p>} 160 </form> 161 ) 162}`, 163 }); 164 </script> 165 </body> 166</html>