forked from
danabra.mov/rscexplorer
A tool for people curious about the React Server Components protocol
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>