Components
Components are functions that return JSX. They're the building blocks of your UI, letting you split the interface into reusable, independent pieces.
Defining Components
A component is a function that returns JSX:
function Greeting() {
return <h1>Hello, World!</h1>;
}
// Use it like an HTML tag
<Greeting />
Components must start with a capital letter. Lowercase names are treated as HTML elements.
Props
Pass data to components through props:
function Greeting({ name, className }) {
return (
<h1 className={className}>
Hello, {name}!
</h1>
);
}
// Usage
<Greeting name="Alice" className="title" />
Default Values
Use destructuring defaults for optional props:
function Button({
variant = 'primary',
size = 'medium',
children
}) {
return (
<button className={`btn btn-${variant} btn-${size}`}>
{children}
</button>
);
}
// Uses defaults
<Button>Click me</Button>
// Override defaults
<Button variant="secondary" size="large">Submit</Button>
Spreading Props
Pass remaining props to child elements:
function Input({ label, ...rest }) {
return (
<label>
{label}
<input {...rest} />
</label>
);
}
<Input
label="Email"
type="email"
placeholder="you@example.com"
required
/>
Children
Content between component tags is passed as children:
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">
{children}
</div>
</div>
);
}
<Card title="Welcome">
<p>This is the card content.</p>
<button>Learn More</button>
</Card>
State in Components
Use signals for component state:
import { signal } from 'what-framework';
function Counter() {
const count = signal(0);
return (
<div>
<span>Count: {count()}</span>
<button onClick={() => count.set(c => c + 1)}>
Increment
</button>
</div>
);
}
Components Run Once
Unlike React, What components only run once when first rendered. Updates happen through signals, not by re-running the component function. This is a key difference from React's mental model.
Composition Patterns
Compound Components
Create related components that work together:
function Tabs({ children }) {
const activeTab = signal(0);
return (
<div className="tabs">
{children(activeTab)}
</div>
);
}
function TabList({ activeTab, children }) {
return (
<div className="tab-list">
{children.map((child, i) => (
<button
className={() => activeTab() === i ? 'active' : ''}
onClick={() => activeTab.set(i)}
>
{child}
</button>
))}
</div>
);
}
function TabPanels({ activeTab, children }) {
return (
<div className="tab-panels">
{() => children[activeTab()]}
</div>
);
}
Render Props
Pass a function as children for flexible rendering:
function Toggle({ children }) {
const on = signal(false);
const toggle = () => on.set(v => !v);
return children({ on, toggle });
}
// Usage
<Toggle>
{({ on, toggle }) => (
<button onClick={toggle}>
{() => on() ? 'ON' : 'OFF'}
</button>
)}
</Toggle>
How JSX Compiles
What Framework uses an optimizing compiler that transforms JSX into direct DOM operations. The compiler extracts static HTML into cloneable templates and wraps dynamic parts in fine-grained effects.
The pipeline works like this:
// Your JSX:
<div className="card">
<p>Count: {count()}</p>
</div>
// Compiler output (simplified):
const _tmpl = template('<div class="card"><p>Count: </p></div>');
function render() {
const _el = _tmpl.cloneNode(true);
insert(_el.firstChild, () => count());
return _el;
}
// Pipeline: JSX -> compiler -> template() + insert() + effect() -> DOM
Fine-Grained Rendering
Static HTML is extracted into templates that are cloned once. Dynamic expressions like {count()} are wrapped in effects that update only the specific DOM nodes that depend on them. There is no virtual DOM, no diffing, and no re-rendering of the entire component. The compiler does the work at build time so the runtime stays minimal.
JSX Basics
JSX is syntactic sugar for creating elements. Here are the key things to know:
Expressions
Use curly braces for JavaScript expressions:
const name = 'Alice';
const items = ['Apple', 'Banana', 'Cherry'];
<div>
{/* Variables */}
<h1>Hello, {name}!</h1>
{/* Expressions */}
<p>{1 + 1}</p>
{/* Function calls */}
<p>{name.toUpperCase()}</p>
{/* Arrays */}
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
</div>
Attributes
Some differences from HTML:
// className instead of class
<div className="container">
// camelCase for multi-word attributes
<input tabIndex=1 autoFocus />
// style as an object
<div style={{ color: 'red', fontSize: 16 }}>
// boolean attributes
<input disabled />
<input disabled={true} />
<input disabled={isDisabled()} />
Fragments
Return multiple elements without a wrapper:
function List() {
return (
<>
<li>First</li>
<li>Second</li>
<li>Third</li>
</>
);
}
Best Practices
1. Keep Components Small
If a component gets too large, split it into smaller pieces:
// Instead of one large component
function Dashboard() {
return (
<div>
<Header />
<Sidebar />
<MainContent />
<Footer />
</div>
);
}
2. Lift State Up When Needed
If two components need to share state, lift it to their common parent:
function Parent() {
const value = signal('');
return (
<div>
<Input value={value} />
<Display value={value} />
</div>
);
}
function Input({ value }) {
return <input value={value} onInput={e => value.set(e.target.value)} />;
}
function Display({ value }) {
return <p>You typed: {value}</p>;
}
3. Pass Signals, Not Values
For reactive props, pass the signal itself:
// GOOD - Child can update the signal
<Counter count={count} />
function Counter({ count }) {
return <button onClick={() => count.set(c => c + 1)}>{count()}</button>;
}