Control Flow

Components for conditional rendering and lists. Cleaner than ternaries and .map() calls.

Show

Conditionally render content based on a condition:

import { Show } from 'what-framework';

function UserGreeting({ user }) {
  return (
    <Show when={user} fallback={<p>Please log in</p>}>
      <p>Welcome back, {() => user().name}!</p>
    </Show>
  );
}

How Show Works

  • when — The condition to check. Can be a signal or value.
  • fallback — (Optional) What to render when condition is false.
  • Children render only when when is truthy.

With Signals

const isLoggedIn = signal(false);

<Show when={isLoggedIn} fallback={<LoginForm />}>
  <Dashboard />
</Show>

// Toggle login state
isLoggedIn.set(true);  // Now shows Dashboard

Why Show instead of ternary?

Show is optimized for fine-grained reactivity. It only re-evaluates when the condition changes, and it properly handles cleanup when toggling between states.

For

Efficiently render a list of items:

import { For } from 'what-framework';

const items = signal([
  { id: 1, name: 'Apple' },
  { id: 2, name: 'Banana' },
  { id: 3, name: 'Cherry' },
]);

<ul>
  <For each={items}>
    {(item, index) => (
      <li key={item.id}>
        {index + 1}. {item.name}
      </li>
    )}
  </For>
</ul>

How For Works

  • each — The array to iterate (can be a signal)
  • Children is a function receiving (item, index)
  • Always provide a key for efficient updates

Empty State

<For each={items} fallback={<p>No items yet</p>}>
  {item => <li>{item.name}</li>}
</For>

Reactive Updates

const todos = signal([]);

// Add item - list updates automatically
todos.set(t => [...t, { id: Date.now(), text: 'New todo' }]);

// Remove item
todos.set(t => t.filter(item => item.id !== idToRemove));

// Update item
todos.set(t => t.map(item =>
  item.id === id ? { ...item, done: true } : item
));

Switch / Match

Render one of multiple options based on conditions:

import { Switch, Match } from 'what-framework';

const status = signal('loading');

<Switch fallback={<p>Unknown status</p>}>
  <Match when={() => status() === 'loading'}>
    <Spinner />
  </Match>
  <Match when={() => status() === 'error'}>
    <ErrorMessage />
  </Match>
  <Match when={() => status() === 'success'}>
    <Content />
  </Match>
</Switch>

With Data

Match can pass data to children:

const result = signal({ type: 'success', data: [...] });

<Switch>
  <Match when={() => result().type === 'success' && result().data}>
    {data => <DataTable items={data} />}
  </Match>
  <Match when={() => result().type === 'error'}>
    <Error />
  </Match>
</Switch>

Portal

Render children into a different DOM node:

import { Portal } from 'what-framework';

function Modal({ isOpen, onClose, children }) {
  return (
    <Show when={isOpen}>
      <Portal mount={document.body}>
        <div className="modal-overlay" onClick={onClose}>
          <div className="modal" onClick={e => e.stopPropagation()}>
            {children}
          </div>
        </div>
      </Portal>
    </Show>
  );
}

Common uses for Portal:

  • Modals and dialogs
  • Tooltips and popovers
  • Dropdown menus
  • Notifications/toasts

ErrorBoundary

Catch errors in child components:

import { ErrorBoundary } from 'what-framework';

<ErrorBoundary fallback={(err, reset) => (
  <div className="error">
    <p>Something went wrong: {err.message}</p>
    <button onClick={reset}>Try again</button>
  </div>
)}>
  <RiskyComponent />
</ErrorBoundary>

Comparison with Plain JS

Pattern Plain JS What Component
Conditional {cond ? <A/> : <B/>} <Show when={cond} fallback={<B/>}><A/></Show>
List {items.map(i => <Li/>)} <For each={items}>{i => <Li/>}</For>
Multi-cond {a ? <A/> : b ? <B/> : <C/>} <Switch><Match>...</Match></Switch>

When to use components vs plain JS

Control flow components are optimized for reactive updates and provide better semantics. Use them when the condition depends on signals. For static conditions known at render time, plain JS works fine.