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>;
}