And Now You Understand React Server Components
(wish me luck)
Let's wake up
Your brain needs this π§
Let's build a framework with
React Server Components!
build underscore.js from scratch
The Rules
- No Bundler
- No TypeScript
- No JSX Transform
- No optimizations
- No deps*
*Only official react, react-error-boundary, and hono.js
import { createElement as h } from 'react'
What we're building
- API:Β to fetch and render Server Components
- Compiler:Β to transform Client Components*
- Router:Β to route on the client and fetch updated Server Components
*It's not really a compiler, but a node loader (runtime module transformer)
Taken for granted
- You're already optimistic about RSCs
- You know RSC basics ("use client" etc.)
- You're smart enough to not do this in prod
- You're willing to dive in for details later
Coming to EpicReact.devΒ soon
The App
SPA
No SSG
No client router yet
The Project
// ui/app.js
import { Suspense, createElement as h, use } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from './app.js'
const initialLocation = location.pathname + location.search
const initialDataPromise = fetch(`/api${initialLocation}`)
.then(r => r.json())
function Root() {
const { shipId, search, ship, shipResults } = use(initialDataPromise)
return h(App, { shipId, search, ship, shipResults })
}
// ...
// server/app.js
// ...
app.use('/*', serveStatic({ root: './public', index: '' }))
app.use(
'/ui/*',
serveStatic({
root: './ui',
onNotFound: (path, context) => context.text('File not found', 404),
rewriteRequestPath: path => path.replace('/ui', ''),
}),
)
// ...
app.get('/api/:shipId?', async context => {
const shipId = context.req.param('shipId') || null
const search = context.req.query('search') || ''
const ship = shipId ? await getShip({ shipId }) : null
const shipResults = await searchShips({ search })
const data = { shipId, search, ship, shipResults }
return context.json(data)
})
app.get('/:shipId?', async context => {
const html = await readFile('./public/index.html', 'utf8')
return context.html(html, 200)
})
// ...
API β‘οΈ RSC
{
"shipId": "d3b8aa65ffe6c",
"search": "h",
"ship": {
"id": "d3b8aa65ffe6c",
"name": "Planet Hopper",
"image": "/ships/d3b8aa65ffe6c.webp",
"topSpeed": 4,
"hyperdrive": false,
"weapons": [
{
"name": "Laser",
"type": "beam",
"damage": 10
}
]
},
"shipResults": {
"ships": [
{
"name": "Star Hopper",
"id": "3ba8aa65ffe6c"
},
{
"name": "Planet Hopper",
"id": "d3b8aa65ffe6c"
},
{
"name": "Stealth Cruiser",
"id": "6c86fca8b9086"
},
{
"name": "Battleship",
"id": "fdc13cb488bf1"
},
{
"name": "Dreadnought",
"id": "d486d48b82b81"
},
{
"name": "Scout Ship",
"id": "ec7a3f950f99f"
},
{
"name": "Transport Ship",
"id": "670003aed3795"
},
{
"name": "Gunship",
"id": "b442531ea32b2"
},
{
"name": "Mining Ship",
"id": "627c497212456"
},
{
"name": "Research Vessel",
"id": "441f7092a8d44"
},
{
"name": "Medical Ship",
"id": "0268fc4817ad1"
},
{
"name": "Cargo Ship",
"id": "1ae7b4b92036b"
}
]
}
}
/api/d3b8aa65ffe6c?search=h
1:{"name":"App","env":"Server","owner":null}
0:D"$1"
3:{"name":"SearchResults","env":"Server","owner":"$1"}
2:D"$3"
2:[["$","li","Star Hopper",{"children":["$","a",null,{"href":"/3ba8aa65ffe6c?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/3ba8aa65ffe6c.webp?size=20","alt":"Star Hopper"},"$3"],"Star Hopper"]},"$3"]},"$3"],["$","li","Planet Hopper",{"children":["$","a",null,{"href":"/d3b8aa65ffe6c?search=h","style":{"fontWeight":"bold"},"children":[["$","img",null,{"src":"/img/ships/d3b8aa65ffe6c.webp?size=20","alt":"Planet Hopper"},"$3"],"Planet Hopper"]},"$3"]},"$3"],["$","li","Stealth Cruiser",{"children":["$","a",null,{"href":"/6c86fca8b9086?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/6c86fca8b9086.webp?size=20","alt":"Stealth Cruiser"},"$3"],"Stealth Cruiser"]},"$3"]},"$3"],["$","li","Battleship",{"children":["$","a",null,{"href":"/fdc13cb488bf1?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/fdc13cb488bf1.webp?size=20","alt":"Battleship"},"$3"],"Battleship"]},"$3"]},"$3"],["$","li","Dreadnought",{"children":["$","a",null,{"href":"/d486d48b82b81?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/d486d48b82b81.webp?size=20","alt":"Dreadnought"},"$3"],"Dreadnought"]},"$3"]},"$3"],["$","li","Scout Ship",{"children":["$","a",null,{"href":"/ec7a3f950f99f?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=20","alt":"Scout Ship"},"$3"],"Scout Ship"]},"$3"]},"$3"],["$","li","Transport Ship",{"children":["$","a",null,{"href":"/670003aed3795?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/670003aed3795.webp?size=20","alt":"Transport Ship"},"$3"],"Transport Ship"]},"$3"]},"$3"],["$","li","Gunship",{"children":["$","a",null,{"href":"/b442531ea32b2?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/b442531ea32b2.webp?size=20","alt":"Gunship"},"$3"],"Gunship"]},"$3"]},"$3"],["$","li","Mining Ship",{"children":["$","a",null,{"href":"/627c497212456?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/627c497212456.webp?size=20","alt":"Mining Ship"},"$3"],"Mining Ship"]},"$3"]},"$3"],["$","li","Research Vessel",{"children":["$","a",null,{"href":"/441f7092a8d44?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/441f7092a8d44.webp?size=20","alt":"Research Vessel"},"$3"],"Research Vessel"]},"$3"]},"$3"],["$","li","Medical Ship",{"children":["$","a",null,{"href":"/0268fc4817ad1?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/0268fc4817ad1.webp?size=20","alt":"Medical Ship"},"$3"],"Medical Ship"]},"$3"]},"$3"],["$","li","Cargo Ship",{"children":["$","a",null,{"href":"/1ae7b4b92036b?search=h","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/1ae7b4b92036b.webp?size=20","alt":"Cargo Ship"},"$3"],"Cargo Ship"]},"$3"]},"$3"]]
5:{"name":"ShipDetails","env":"Server","owner":"$1"}
4:D"$5"
4:["$","div",null,{"className":"ship-info","children":[["$","div",null,{"className":"ship-info__img-wrapper","children":["$","img",null,{"src":"/img/ships/d3b8aa65ffe6c.webp?size=200","alt":"Planet Hopper"},"$5"]},"$5"],["$","section",null,{"children":["$","h2",null,{"children":"Planet Hopper"},"$5"]},"$5"],["$","div",null,{"children":["Top Speed: ",4," ",["$","small",null,{"children":"lyh"},"$5"]]},"$5"],["$","section",null,{"children":["$","ul",null,{"children":[["$","li","Laser",{"children":[["$","label",null,{"children":"Laser"},"$5"],":"," ",["$","span",null,{"children":[10," ",["$","small",null,{"children":["(","beam",")"]},"$5"]]},"$5"]]},"$5"]]},"$5"]},"$5"]]},"$5"]
0:["$","div",null,{"className":"app","children":[["$","div",null,{"className":"search","children":[["$","form",null,{"children":["$","input",null,{"name":"search","placeholder":"Filter ships...","type":"search","defaultValue":"h","autoFocus":true},"$1"]},"$1"],["$","ul",null,{"children":"$2"},"$1"]]},"$1"],["$","div",null,{"className":"details","children":"$4"},"$1"]]},"$1"]
/rsc/d3b8aa65ffe6c?search=h
<App />
Β β‘οΈ renderToPipeableStream
Β β‘οΈ 1:{"name":"App"...
Β β‘οΈ {type:"div",props:{....
Β β‘οΈ createFromFetch
Β β‘οΈ root.render
Β β‘οΈ <div>...
Async Components π
Streaming π
Server Context π§
You'll figure it out...
"use client" πͺ©
import { createElement as h } from 'react'
import { getShip } from '../db/ship-api.js'
import { shipDataStorage } from '../server/async-storage.js'
import { EditableText } from './edit-text.js'
import { getImageUrlForShip } from './img-utils.js'
export async function ShipDetails() {
const { shipId } = shipDataStorage.getStore()
const ship = await getShip({ shipId })
const shipImgSrc = getImageUrlForShip(ship.id, { size: 200 })
return (
<div className="ship-info">
<div className="ship-info__img-wrapper">
<img src={shipImgSrc} alt={ship.name} />
</div>
<section>
<h2>
<EditableText
key={shipId}
shipId={shipId}
initialValue={ship.name}
/>
</h2>
</section>
{/* ... */}
</div>
)
}
'use client'
import { useRef, useState } from 'react'
import { flushSync } from 'react-dom'
// ...
export function EditableText({ id, shipId, initialValue = '' }) {
const [edit, setEdit] = useState(false)
const [value, setValue] = useState(initialValue)
const inputRef = useRef(null)
const buttonRef = useRef(null)
return (
<div>
{edit ? (
<form
action={() => {
setValue(inputRef.current?.value ?? '')
flushSync(() => {
setEdit(false)
})
buttonRef.current?.focus()
// TODO: persist the value
}}
>
{/* ... */}
</form>
) : (
<button
onClick={() => {
flushSync(() => {
setEdit(true)
})
inputRef.current?.select()
}}
>
{/* ... */}
</button>
)}
</div>
)
}
2:"$Sreact.suspense"
1:{"name":"App","env":"Server","owner":null}
0:D"$1"
4:{"name":"SearchResultsFallback","env":"Server","owner":"$1"}
3:D"$4"
3:[["$","li","0",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","1",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","2",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","3",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","4",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","5",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","6",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","7",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","8",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","9",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","10",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","11",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"]]
6:{"name":"SearchResults","env":"Server","owner":"$1"}
5:D"$6"
8:{"name":"ShipFallback","env":"Server","owner":"$1"}
7:D"$8"
7:["$","div",null,{"className":"ship-info","children":[["$","div",null,{"className":"ship-info__img-wrapper","children":["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=200","alt":"ec7a3f950f99f"},"$8"]},"$8"],["$","section",null,{"children":["$","h2",null,{"children":"Loading..."},"$8"]},"$8"],["$","div",null,{"children":["Top Speed: XX"," ",["$","small",null,{"children":"lyh"},"$8"]]},"$8"],["$","section",null,{"children":["$","ul",null,{"children":[["$","li","0",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"],["$","li","1",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"],["$","li","2",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"]]},"$8"]},"$8"]]},"$8"]
a:{"name":"ShipDetails","env":"Server","owner":"$1"}
9:D"$a"
0:["$","div",null,{"className":"app","children":[["$","div",null,{"className":"search","children":[["$","form",null,{"children":["$","input",null,{"name":"search","placeholder":"Filter ships...","type":"search","defaultValue":"","autoFocus":true},"$1"]},"$1"],["$","ul",null,{"children":["$","$2",null,{"fallback":"$3","children":"$L5"},"$1"]},"$1"]]},"$1"],["$","div",null,{"className":"details","children":["$","$2",null,{"fallback":"$7","children":"$L9"},"$1"]},"$1"]]},"$1"]
b:I["/edit-text.js","EditableText"]
9:["$","div",null,{"className":"ship-info","children":[["$","div",null,{"className":"ship-info__img-wrapper","children":["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=200","alt":"Scout Ship"},null]},null],["$","section",null,{"children":["$","h2",null,{"children":["$","$Lb","ec7a3f950f99f",{"shipId":"ec7a3f950f99f","initialValue":"Scout Ship"},null]},null]},null],["$","div",null,{"children":["Top Speed: ",11," ",["$","small",null,{"children":"lyh"},null]]},null],["$","section",null,{"children":["$","p",null,{"children":"NOTE: This ship is not equipped with any weapons."},null]},null]]},null]
5:[["$","li","Infinity Drifter",{"children":["$","a",null,{"href":"/bc4cbadf89bd3","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/bc4cbadf89bd3.webp?size=20","alt":"Infinity Drifter"},null],"Infinity Drifter"]},null]},null],["$","li","Star Hopper",{"children":["$","a",null,{"href":"/3ba8aa65ffe6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/3ba8aa65ffe6c.webp?size=20","alt":"Star Hopper"},null],"Star Hopper"]},null]},null],["$","li","Galaxy Cruiser",{"children":["$","a",null,{"href":"/ab267a5984523","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/ab267a5984523.webp?size=20","alt":"Galaxy Cruiser"},null],"Galaxy Cruiser"]},null]},null],["$","li","Planet Hopper",{"children":["$","a",null,{"href":"/d3b8aa65ffe6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/d3b8aa65ffe6c.webp?size=20","alt":"Planet Hopper"},null],"Planet Hopper"]},null]},null],["$","li","Space Taxi",{"children":["$","a",null,{"href":"/1ff1991efe029","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/1ff1991efe029.webp?size=20","alt":"Space Taxi"},null],"Space Taxi"]},null]},null],["$","li","Star Destroyer",{"children":["$","a",null,{"href":"/f3d9a88e1c234","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/f3d9a88e1c234.webp?size=20","alt":"Star Destroyer"},null],"Star Destroyer"]},null]},null],["$","li","Interceptor",{"children":["$","a",null,{"href":"/cb03cc4e5717e","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/cb03cc4e5717e.webp?size=20","alt":"Interceptor"},null],"Interceptor"]},null]},null],["$","li","Stealth Cruiser",{"children":["$","a",null,{"href":"/6c86fca8b9086","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/6c86fca8b9086.webp?size=20","alt":"Stealth Cruiser"},null],"Stealth Cruiser"]},null]},null],["$","li","Battleship",{"children":["$","a",null,{"href":"/fdc13cb488bf1","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/fdc13cb488bf1.webp?size=20","alt":"Battleship"},null],"Battleship"]},null]},null],["$","li","Dreadnought",{"children":["$","a",null,{"href":"/d486d48b82b81","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/d486d48b82b81.webp?size=20","alt":"Dreadnought"},null],"Dreadnought"]},null]},null],["$","li","Cruiser",{"children":["$","a",null,{"href":"/cfd10fcd2de6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/cfd10fcd2de6c.webp?size=20","alt":"Cruiser"},null],"Cruiser"]},null]},null],["$","li","Frigate",{"children":["$","a",null,{"href":"/e92cefe4f6727","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/e92cefe4f6727.webp?size=20","alt":"Frigate"},null],"Frigate"]},null]},null],["$","li","Scout Ship",{"children":["$","a",null,{"href":"/ec7a3f950f99f","style":{"fontWeight":"bold"},"children":[["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=20","alt":"Scout Ship"},null],"Scout Ship"]},null]},null]]
// ui/edit-text.js
'use client'
import { createElement as h, useRef, useState } from 'react'
import { flushSync } from 'react-dom'
// ...
export function EditableText({ id, shipId, initialValue = '' }) {
// ...
}
// π π π π π π
import {registerClientReference} from "react-server-dom-esm/server";
export const EditableText = registerClientReference(function() {throw new Error("Attempted to call EditableText() from the server but EditableText is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.");},"file:///Users/kentcdodds/code/epicweb-dev/react-server-components/playground/ui/edit-text.js","EditableText");
// EditableText is the function with these properties:
{
"$$typeof": "Symbol(react.client.reference)",
"$$id": "file:///Users/kentcdodds/code/epicweb-dev/react-server-components/exercises/03.client-components/01.solution.loader/ui/edit-text.js#EditableText"
}
2:"$Sreact.suspense"
1:{"name":"App","env":"Server","owner":null}
0:D"$1"
4:{"name":"SearchResultsFallback","env":"Server","owner":"$1"}
3:D"$4"
3:[["$","li","0",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","1",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","2",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","3",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","4",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","5",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","6",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","7",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","8",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","9",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","10",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"],["$","li","11",{"children":["$","a",null,{"href":"#","children":[["$","img",null,{"src":"/img/fallback-ship.png","alt":"loading"},"$4"],"... loading"]},"$4"]},"$4"]]
6:{"name":"SearchResults","env":"Server","owner":"$1"}
5:D"$6"
8:{"name":"ShipFallback","env":"Server","owner":"$1"}
7:D"$8"
7:["$","div",null,{"className":"ship-info","children":[["$","div",null,{"className":"ship-info__img-wrapper","children":["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=200","alt":"ec7a3f950f99f"},"$8"]},"$8"],["$","section",null,{"children":["$","h2",null,{"children":"Loading..."},"$8"]},"$8"],["$","div",null,{"children":["Top Speed: XX"," ",["$","small",null,{"children":"lyh"},"$8"]]},"$8"],["$","section",null,{"children":["$","ul",null,{"children":[["$","li","0",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"],["$","li","1",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"],["$","li","2",{"children":[["$","label",null,{"children":"loading"},"$8"],":"," ",["$","span",null,{"children":["XX ",["$","small",null,{"children":"(loading)"},"$8"]]},"$8"]]},"$8"]]},"$8"]},"$8"]]},"$8"]
a:{"name":"ShipDetails","env":"Server","owner":"$1"}
9:D"$a"
0:["$","div",null,{"className":"app","children":[["$","div",null,{"className":"search","children":[["$","form",null,{"children":["$","input",null,{"name":"search","placeholder":"Filter ships...","type":"search","defaultValue":"","autoFocus":true},"$1"]},"$1"],["$","ul",null,{"children":["$","$2",null,{"fallback":"$3","children":"$L5"},"$1"]},"$1"]]},"$1"],["$","div",null,{"className":"details","children":["$","$2",null,{"fallback":"$7","children":"$L9"},"$1"]},"$1"]]},"$1"]
b:I["/edit-text.js","EditableText"]
9:["$","div",null,{"className":"ship-info","children":[["$","div",null,{"className":"ship-info__img-wrapper","children":["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=200","alt":"Scout Ship"},null]},null],["$","section",null,{"children":["$","h2",null,{"children":["$","$Lb","ec7a3f950f99f",{"shipId":"ec7a3f950f99f","initialValue":"Scout Ship"},null]},null]},null],["$","div",null,{"children":["Top Speed: ",11," ",["$","small",null,{"children":"lyh"},null]]},null],["$","section",null,{"children":["$","p",null,{"children":"NOTE: This ship is not equipped with any weapons."},null]},null]]},null]
5:[["$","li","Infinity Drifter",{"children":["$","a",null,{"href":"/bc4cbadf89bd3","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/bc4cbadf89bd3.webp?size=20","alt":"Infinity Drifter"},null],"Infinity Drifter"]},null]},null],["$","li","Star Hopper",{"children":["$","a",null,{"href":"/3ba8aa65ffe6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/3ba8aa65ffe6c.webp?size=20","alt":"Star Hopper"},null],"Star Hopper"]},null]},null],["$","li","Galaxy Cruiser",{"children":["$","a",null,{"href":"/ab267a5984523","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/ab267a5984523.webp?size=20","alt":"Galaxy Cruiser"},null],"Galaxy Cruiser"]},null]},null],["$","li","Planet Hopper",{"children":["$","a",null,{"href":"/d3b8aa65ffe6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/d3b8aa65ffe6c.webp?size=20","alt":"Planet Hopper"},null],"Planet Hopper"]},null]},null],["$","li","Space Taxi",{"children":["$","a",null,{"href":"/1ff1991efe029","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/1ff1991efe029.webp?size=20","alt":"Space Taxi"},null],"Space Taxi"]},null]},null],["$","li","Star Destroyer",{"children":["$","a",null,{"href":"/f3d9a88e1c234","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/f3d9a88e1c234.webp?size=20","alt":"Star Destroyer"},null],"Star Destroyer"]},null]},null],["$","li","Interceptor",{"children":["$","a",null,{"href":"/cb03cc4e5717e","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/cb03cc4e5717e.webp?size=20","alt":"Interceptor"},null],"Interceptor"]},null]},null],["$","li","Stealth Cruiser",{"children":["$","a",null,{"href":"/6c86fca8b9086","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/6c86fca8b9086.webp?size=20","alt":"Stealth Cruiser"},null],"Stealth Cruiser"]},null]},null],["$","li","Battleship",{"children":["$","a",null,{"href":"/fdc13cb488bf1","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/fdc13cb488bf1.webp?size=20","alt":"Battleship"},null],"Battleship"]},null]},null],["$","li","Dreadnought",{"children":["$","a",null,{"href":"/d486d48b82b81","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/d486d48b82b81.webp?size=20","alt":"Dreadnought"},null],"Dreadnought"]},null]},null],["$","li","Cruiser",{"children":["$","a",null,{"href":"/cfd10fcd2de6c","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/cfd10fcd2de6c.webp?size=20","alt":"Cruiser"},null],"Cruiser"]},null]},null],["$","li","Frigate",{"children":["$","a",null,{"href":"/e92cefe4f6727","style":{"fontWeight":"normal"},"children":[["$","img",null,{"src":"/img/ships/e92cefe4f6727.webp?size=20","alt":"Frigate"},null],"Frigate"]},null]},null],["$","li","Scout Ship",{"children":["$","a",null,{"href":"/ec7a3f950f99f","style":{"fontWeight":"bold"},"children":[["$","img",null,{"src":"/img/ships/ec7a3f950f99f.webp?size=20","alt":"Scout Ship"},null],"Scout Ship"]},null]},null]]
ui updates π§
more problems π
Pending UI
History
Caching
* aka react router v7
*
actions β‘
... maybe next time π
π
π
π
π
π
π
π
π
π
And Now You Understand React Server Components
(hopefully)
You are fantastic
Thank you!
π @kentcdodds
And Now You Understand React Server Components
By Kent C. Dodds
And Now You Understand React Server Components
You want to keep up with the future of React. React has evolved over the years and continues to push the component model further and further. Out of all the evolutions of React, server components are certainly the biggest advancement. It expands the component model further than ever before and as a result, requires some rethinking. Seasoned React developers need to unlearn the way we used to do things to be able to understand the improvements that React Server Components offer. In this talk, Kent will guide you through React Server Components start to finish so you can understand how React Server Components work and set you off on your journey into the future of components everywhere!
- 1,957