Typed props
The island is TypeScript, and the type of its props is written by pad types from the page's own data: the order the PHP makes, captured as the page's sample. A field the PHP does not have fails the check, not the visitor.
pad types_samples/$padExpose
.tsxtsc --noEmit
Live
Order 1042 for Ada Fernández
How it is built
PAD, on the server the islands, built by Vite
<div data-island="OrderCard" data-props="{^order}">
<p>Order {$order.number} for {$order.customer.name}</p>
</div><?php
// An order as a page's PHP makes it - from a database, here an array. The island gets it as
// its props, and its component, in TypeScript, knows its shape from the types pad types
// wrote: the sample of this page, captured from this very data.
$order = [ 'number' => 1042,
'status' => 'shipped',
'paid' => TRUE,
'note' => NULL,
'customer' => [ 'name' => 'Ada Fernández', 'city' => 'Amsterdam' ],
'lines' => [ [ 'product' => 'Aurora Headphones', 'quantity' => 1, 'price' => 189.0 ],
[ 'product' => 'Tactile Keyboard', 'quantity' => 2, 'price' => 119.0, 'gift' => TRUE ] ] ];
$padExpose = [ 'order' ];
?>// The order card, in TypeScript: its props are the order the page's PHP made, and their type
// is the one pad types wrote from the page's own data - src/types/pad.d.ts. A field the PHP
// does not have, or one used as the wrong type, fails npm run check before it fails a visitor.
import { useState } from 'react';
import type { ExamplesTypedVars } from '../types/pad';
type Order = ExamplesTypedVars['order'];
type Line = Order['lines'][number];
const money = (amount: number) => '€ ' + amount.toFixed(2);
export default function OrderCard(order: Order) {
const [open, setOpen] = useState<Line | null>(null);
const total = order.lines.reduce((sum, line) => sum + line.quantity * line.price, 0);
return (
<div className="card order">
<div className="framework-head">
<strong>Order #{order.number}</strong>
<span className={'badge' + (order.paid ? ' is-good' : '')}>{order.status}{order.paid ? ' · paid' : ''}</span>
</div>
<p className="muted small">{order.customer.name}, {order.customer.city}</p>
<ul className="order-lines">
{order.lines.map((line) => (
<li key={line.product} onClick={() => setOpen(open === line ? null : line)}>
<span>{line.quantity} × {line.product}{line.gift ? ' 🎁' : ''}</span>
<span>{money(line.quantity * line.price)}</span>
</li>
))}
</ul>
<div className="order-total"><span>Total</span><strong>{money(total)}</strong></div>
{order.note === null && <p className="muted small">No note: the order had none, so its type is null - capture an order with one, and it becomes string.</p>}
<p className="framework-tag">React · TypeScript</p>
</div>
);
}// The types of what the pages of islands hold - written by pad types islands examples/typed.
// Made from the data itself: run the command again when a page's data changes.
// examples/typed - the variables of its PHP
export interface ExamplesTypedVars {
title: string;
navSection: string;
islandsCss: number;
example: {
page: string;
title: string;
icon: string;
lede: string;
server: unknown[];
client: unknown[];
files: unknown[];
groupTitle: string;
prev: {
page: string;
title: string;
};
next: {
page: string;
title: string;
};
};
serverChips: unknown[];
clientChips: unknown[];
prev: {
page: string;
title: string;
};
next: {
page: string;
title: string;
};
sourceFiles: string;
order: {
number: number;
status: string;
paid: boolean;
note: null;
customer: {
name: string;
city: string;
};
lines: {
product: string;
quantity: number;
price: number;
gift?: boolean;
}[];
};
}
// examples/typed - its JSON answer, ?examples/typed&padFormat=json
export interface ExamplesTypedAnswer {
order: {
number: number;
status: string;
paid: boolean;
note: null;
customer: {
name: string;
city: string;
};
lines: {
product: string;
quantity: number;
price: number;
gift?: boolean;
}[];
};
}{
"title": "Typed - PAD + islands",
"navSection": "index",
"islandsCss": 1791452182,
"example": {
"page": "examples/typed",
"title": "Typed",
"icon": "",
"lede": "",
"server": [],
"client": [],
"files": [],
"groupTitle": "Examples",
"prev": {
"page": "",
"title": ""
},
"next": {
"page": "",
"title": ""
}
},
"serverChips": [],
"clientChips": [],
"prev": {
"page": "",
"title": ""
},
"next": {
"page": "",
"title": ""
},
"sourceFiles": "",
"order": {
"number": 1042,
"status": "shipped",
"paid": true,
"note": null,
"customer": {
"name": "Ada Fernández",
"city": "Amsterdam"
},
"lines": [
{
"product": "Aurora Headphones",
"quantity": 1,
"price": 189
},
{
"product": "Tactile Keyboard",
"quantity": 2,
"price": 119,
"gift": true
}
]
}
}