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
whenis 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
keyfor 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.