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