Overview
Props in Mates are passed as Props<T> — a function, not a plain object. When you write x(Greeting, { name: name() }), Mates wraps those values in a getter that always returns the latest snapshot. Call propsFn() inside the inner (template) function to read them reactively.
Because the inner function re-runs on every reactive change, calling propsFn() there always gets the most recent values the parent passed down — including any atoms the parent read when building the props object.
TypeScript types: annotate the component's first parameter as Props<{ name: string; count?: number }>. The Props<T> alias is simply () => T, so destructuring with defaults inside the inner function is idiomatic and safe.
Where to call propsFn()
|
Location
|
Safe?
|
Why
|
|---|---|---|
| Inside the inner function (return value) | correct | Runs on every reactive update — always returns the latest props from the parent. |
| In the outer function (setup phase) | stale | Outer runs once. Values captured here are frozen at mount time and never reflect parent updates. |
| Inside an event handler defined in outer | correct |
Handlers close over propsFn (the function reference), not a snapshot — calling it at handler time reads the current value.
|
Never destructure props in the outer function
// ❌ WRONG — name is frozen at mount time
const Bad = (propsFn) => {
const { name } = propsFn(); // outer fn runs once — stale forever
return () => html`<p>Hello, ${name}</p>`; // name never updates!
};
// ✅ CORRECT — call propsFn() inside the inner function
const Good = (propsFn) => {
return () => html`<p>Hello, ${propsFn().name}</p>`; // always fresh
};
Passing typed props
Two instances of Greeting share the same
reactive name atom from the parent. One also receives an
optional count prop. Changing the input updates both.
Required vs optional props
label is required; color and
variant fall back to defaults when omitted. Defaults are
applied inside the inner function so every render reads fresh props.