@pyreon/core — API Reference
Generated from
core'ssrc/manifest.ts— the same source that powersllms.txtand MCPget_api. Do not edit this page by hand; edit the manifest. For the conceptual guide, see core.
Component model and lifecycle for Pyreon. Provides the VNode type system, h() hyperscript function, JSX automatic runtime (@pyreon/core/jsx-runtime), lifecycle hooks (onMount, onUnmount), two-tier context system (createContext for static values, createReactiveContext for signal-backed values), control-flow components (Show, Switch/Match, For, Suspense, ErrorBoundary), code-splitting via lazy(), dynamic rendering via Dynamic, and props utilities (splitProps, mergeProps, cx, createUniqueId). Components are plain functions (ComponentFn<P> = (props: P) => VNodeChild) that run ONCE — reactivity comes from reading signals inside reactive scopes, not from re-running the component.
Features
h() — hyperscript function producing VNodes, JSX compiles to h() or _tpl()
Fragment — group children without a wrapper DOM element
onMount / onUnmount — lifecycle hooks, onMount supports cleanup return
createContext / createReactiveContext — two-tier context system
provide / useContext — push and read context values
Show / Switch+Match — reactive conditional rendering
For — keyed reactive list rendering with by prop
Suspense / ErrorBoundary — async and error boundaries
lazy() / Dynamic — code splitting and dynamic component rendering
splitProps / mergeProps / removeUndefinedProps — reactivity-preserving props utilities
cx() — class value combiner (strings, objects, arrays, nested)
createUniqueId — SSR-safe unique ID generation
Complete example
A full, end-to-end usage of the package:
import { h, Fragment, onMount, onUnmount, provide, createContext, createReactiveContext, useContext, Show, Switch, Match, For, Suspense, ErrorBoundary, lazy, Dynamic, cx, splitProps, mergeProps, createUniqueId } from "@pyreon/core"
import { signal, computed } from "@pyreon/reactivity"
// Context — static (destructure-safe) vs reactive (must call to read)
const ThemeCtx = createContext<"light" | "dark">("light")
const ModeCtx = createReactiveContext<"light" | "dark">("light")
const App = (props: { children: any }) => {
const mode = signal<"light" | "dark">("dark")
provide(ThemeCtx, "dark") // static — safe to destructure
provide(ModeCtx, () => mode()) // reactive — consumer must call
return <>{props.children}</>
}
// Lifecycle
const Timer = () => {
const count = signal(0)
onMount(() => {
const id = setInterval(() => count.update(n => n + 1), 1000)
return () => clearInterval(id) // cleanup runs on unmount
})
return <div>{count()}</div>
}
// Control flow — reactive conditional rendering
const Page = (props: { items: { id: number; name: string }[]; loggedIn: () => boolean }) => (
<div>
<Show when={props.loggedIn()} fallback={<p>Please log in</p>}>
<For each={props.items} by={item => item.id}>
{item => <li>{item.name}</li>}
</For>
</Show>
</div>
)
// Props utilities — preserve reactivity
const Button = (props: { class?: string; size?: string; onClick: () => void; children: any }) => {
const [local, rest] = splitProps(props, ["class", "size"])
const merged = mergeProps({ size: "md" }, local)
const id = createUniqueId()
return <button id={id} {...rest} class={cx(["btn", `btn-${merged.size}`, local.class])} />
}
// Code splitting
const HeavyPage = lazy(() => import("./HeavyPage"))
const LazyApp = () => (
<Suspense fallback={<div>Loading...</div>}>
<HeavyPage />
</Suspense>
)Exports
| Symbol | Kind | Summary |
|---|---|---|
h | function | Create a VNode from a component function, HTML tag string, or symbol (Fragment, Portal). |
Fragment | constant | Symbol used as the type for fragment VNodes that group children without producing a wrapper DOM element. |
onMount | function | Register a callback that runs after the component mounts into the DOM. |
onUnmount | function | Register a callback that runs when the component is removed from the DOM. |
onUpdate | function | Register a callback that runs after the component updates (reactive dependencies change and DOM patches complete). |
onErrorCaptured | function | Register an error handler that captures errors thrown by descendant components. |
createContext | function | Create a static context. |
createReactiveContext | function | Create a reactive context. |
provide | function | Push a context value for all descendant components. |
useContext | function | Read the nearest provided value for a context. |
Show | component | Reactive conditional rendering. |
Switch | component | Multi-branch conditional rendering. |
Match | component | A branch inside a <Switch>. |
For | component | Keyed reactive list rendering. |
Suspense | component | Async boundary that shows fallback while any lazy() component or async child inside is loading. |
ErrorBoundary | component | Catches render errors thrown by descendant components. |
lazy | function | Wrap a dynamic import for code splitting. |
Dynamic | component | Renders a component by reference or string tag name. |
cx | function | Combine a class value into a single string. |
useControllableState | function | The controlled/uncontrolled state pattern, as one primitive. |
splitProps | function | Split a props object into two parts: the picked keys and the rest. |
mergeProps | function | Merge multiple props objects with last-source-wins semantics. |
removeUndefinedProps | function | Copy a props object, dropping keys whose DATA value is exactly undefined while preserving every getter-shaped (reactiv |
createUniqueId | function | Generate a unique string ID ("pyreon-1", "pyreon-2", ...) that is consistent between server and client when called in th |
Portal | component | Render children into a DOM element outside the component tree (typically document.body). |
mapArray | function | Low-level reactive array mapping used internally by <For>. |
createRef | function | Create a mutable ref object ({ current: T | null }) for holding DOM element references. |
nativeCompat | function | Mark a Pyreon framework component as "self-managing" so compat layers (@pyreon/{react,preact,vue,solid}-compat) skip t |
isNativeCompat | function | Compat-layer-side: read whether a function has been marked as a Pyreon native framework component via nativeCompat(). |
NATIVE_COMPAT_MARKER | constant | The well-known registry symbol (Symbol.for("pyreon:native-compat")) used to mark a component as a Pyreon native framew |
ExtractProps | type | Extracts the props type from a ComponentFn. |
HigherOrderComponent | type | Typed HOC pattern where HOP is the props the HOC adds and P is the wrapped component's own props. |
API
h function
h<P extends Props>(type: ComponentFn<P> | string | symbol, props: P | null, ...children: VNodeChild[]): VNodeCreate a VNode from a component function, HTML tag string, or symbol (Fragment, Portal). Low-level API — prefer JSX which compiles to h() calls (or _tpl() + _bind() for template-optimized paths). Children are stored in vnode.children; components must merge them via props.children = vnode.children.length === 1 ? vnode.children[0] : vnode.children.
Example
const vnode = h("div", { class: "container" },
h("h1", null, "Hello"),
h(Counter, { initial: 0 })
)Common mistakes
h("div", "text")— second arg is always props (or null). Text children go in the third+ positions:h("div", null, "text")h(MyComponent, { children: <span /> })— children go as rest args, not a prop:h(MyComponent, null, <span />)h("input", { className: "x" })— useclassnotclassName(Pyreon uses standard HTML attributes)h("input", { onChange: handler })— useonInputfor keypress-by-keypress updates (native DOM events)
See also: Fragment · Dynamic · lazy
Fragment constant
Fragment: symbolSymbol used as the type for fragment VNodes that group children without producing a wrapper DOM element. In JSX, <>...</> compiles to h(Fragment, null, ...). Useful when a component needs to return multiple sibling elements.
Example
// JSX:
<>
<h1>Title</h1>
<p>Content</p>
</>
// h() API:
h(Fragment, null, h("h1", null, "Title"), h("p", null, "Content"))See also: h
onMount function
onMount(fn: () => CleanupFn | void): voidRegister a callback that runs after the component mounts into the DOM. The callback can optionally return a cleanup function that runs on unmount — this is the idiomatic pattern for event listeners, timers, and subscriptions. Must be called during component setup (the synchronous function body), not inside effects or async callbacks.
Example
const Timer = () => {
const count = signal(0)
onMount(() => {
const id = setInterval(() => count.update(n => n + 1), 1000)
return () => clearInterval(id) // cleanup on unmount
})
return <div>{count()}</div>
}Common mistakes
Forgetting cleanup:
onMount(() => { const id = setInterval(...) })leaks the interval. Return cleanup:return () => clearInterval(id)Using
onMount+ separateonUnmountfor paired setup/teardown — prefer returning cleanup fromonMountinsteadCalling
onMountinside aneffect()or async callback — it only works during synchronous component setupAccessing DOM refs before mount — the callback runs AFTER mount, which is the right place for DOM measurements
See also: onUnmount · onUpdate
onUnmount function
onUnmount(fn: () => void): voidRegister a callback that runs when the component is removed from the DOM. For paired setup/teardown, prefer returning a cleanup function from onMount instead — it co-locates the cleanup with the setup. onUnmount is useful when cleanup needs to reference state computed separately from the mount callback.
Example
onUnmount(() => {
console.log("Component removed from DOM")
})See also: onMount
onUpdate function
onUpdate(fn: () => void): voidRegister a callback that runs after the component updates (reactive dependencies change and DOM patches complete). Rarely needed — most update logic belongs in effect() or computed(). Useful for imperative DOM measurements that need to run after all reactive updates have flushed.
Example
onUpdate(() => {
console.log("Component updated, DOM is current")
})See also: onMount · onUnmount
onErrorCaptured function
onErrorCaptured(fn: (error: unknown) => boolean | void): voidRegister an error handler that captures errors thrown by descendant components. Return false to prevent the error from propagating further up the tree. Works alongside ErrorBoundary for programmatic error handling.
Example
onErrorCaptured((error) => {
console.error("Caught:", error)
return false // stop propagation
})See also: ErrorBoundary
createContext function
createContext<T>(defaultValue: T): Context<T>Create a static context. useContext() returns the value directly (T), so it is safe to destructure. Use this for values that do not change after being provided (theme name, locale string, config object). For values that change reactively (mode signal, locale signal), use createReactiveContext instead — otherwise consumers capture a stale snapshot at setup time.
Example
const ThemeCtx = createContext<"light" | "dark">("light")
// Provide:
const App = () => {
provide(ThemeCtx, "dark")
return <Child />
}
// Consume:
const Child = () => {
const theme = useContext(ThemeCtx) // "dark" — safe to destructure
return <div class={theme}>...</div>
}Common mistakes
provide(ThemeCtx, () => modeSignal())with a static context — the consumer receives the function itself, not the signal value. UsecreateReactiveContextfor dynamic valuesDestructuring a reactive context value:
const { mode } = useContext(reactiveCtx)captures once. Keep the object reference and access lazilyCalling
useContextoutside a component body — it reads from the component context stack, which only exists during setup
See also: createReactiveContext · provide · useContext
createReactiveContext function
createReactiveContext<T>(defaultValue: T): ReactiveContext<T>Create a reactive context. useContext() returns () => T — an accessor that must be called to read the current value. Use this for values that change over time (mode, locale, user). The accessor subscribes to updates when read inside reactive scopes (effect(), JSX thunks, computed()).
Example
const ModeCtx = createReactiveContext<"light" | "dark">("light")
// Provide:
const App = () => {
const mode = signal<"light" | "dark">("dark")
provide(ModeCtx, () => mode())
return <Child />
}
// Consume:
const Child = () => {
const getMode = useContext(ModeCtx) // () => "dark"
return <div class={getMode()}>...</div>
}See also: createContext · provide · useContext
provide function
provide<T>(ctx: Context<T> | ReactiveContext<T>, value: T): voidPush a context value for all descendant components. Auto-cleans up on unmount. Must be called during component setup (synchronous function body). Preferred over manual pushContext/popContext. For reactive values, provide a getter function to a ReactiveContext: provide(ModeCtx, () => modeSignal()).
Example
const ThemeCtx = createContext<"light" | "dark">("light")
function App() {
provide(ThemeCtx, "dark")
return <Child />
}Common mistakes
provide(ctx, "static")for a value that changes — usecreateReactiveContext+provide(ctx, () => signal())Calling
provideinsideonMountoreffect— it must run during synchronous component setupProviding the same context twice in one component — the second
provideshadows the first for that subtree
See also: createContext · createReactiveContext · useContext
useContext function
useContext<T>(ctx: Context<T>): TRead the nearest provided value for a context. For static Context<T>, returns T directly. For ReactiveContext<T>, returns () => T — must call the accessor to read. Falls back to the default value if no ancestor provides the context.
Example
const theme = useContext(ThemeContext) // static: returns T
const getMode = useContext(ModeCtx) // reactive: returns () => TSee also: provide · createContext · createReactiveContext
Show component
<Show when={condition} fallback={alternative}>{children}</Show>Reactive conditional rendering. Mounts children when when is truthy, unmounts and shows fallback when falsy. More efficient than ternary for signal-driven conditions because it avoids re-evaluating the entire branch expression on every signal change — Show only transitions between mounted/unmounted when the boolean flips. when accepts BOTH a value (when={true}, when={signal()}) and an accessor (when={() => signal()}) — the framework normalizes via typeof === "function". The accessor form is required for true reactivity (the framework re-evaluates it on signal change); a bare when={signal} reference works because the compiler's signal auto-call rewrites it to when={signal()}.
Example
<Show when={isLoggedIn()} fallback={<LoginForm />}>
<Dashboard />
</Show>Common mistakes
{cond() ? <A /> : <B />}— works but less efficient than<Show>for signal-driven conditions<Show when={items().length}>— works (truthy check), but be explicit:<Show when={items().length > 0}><Show when={signal}>(bare reference) — relies on the compiler's signal auto-call to rewrite towhen={signal()}. Works defensively but usewhen={() => signal()}for explicit accessor semantics across the entire reactive lifecycle.
See also: Switch · Match · For
Switch component
<Switch fallback={default}>{Match children}</Switch>Multi-branch conditional rendering. Renders the first <Match> child whose when prop is truthy. If no match, renders the fallback. More readable than nested <Show> for multi-way conditions.
Example
<Switch fallback={<p>Unknown status</p>}>
<Match when={status() === "loading"}>
<Spinner />
</Match>
<Match when={status() === "error"}>
<ErrorDisplay />
</Match>
<Match when={status() === "success"}>
<Results />
</Match>
</Switch>See also: Match · Show
Match component
<Match when={condition}>{children}</Match>A branch inside a <Switch>. Renders its children when when is truthy and it is the first truthy <Match> in the parent <Switch>. Must be a direct child of <Switch>. when accepts both a value and an accessor (same normalization as <Show>).
Example
<Switch>
<Match when={tab() === "home"}><Home /></Match>
<Match when={tab() === "settings"}><Settings /></Match>
</Switch>See also: Switch · Show
For component
<For each={items} by={keyFn}>{renderFn}</For>Keyed reactive list rendering. Uses the by prop (not key) for the key function because JSX extracts key as a special VNode reconciliation prop. The render function receives each item and its index. Internally uses an LIS-based reconciler for minimal DOM mutations when the list changes.
Example
const items = signal([
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
])
<For each={items()} by={item => item.id}>
{(item, index) => <li>{item.name}</li>}
</For>Common mistakes
<For each={items}>— must call the signal:<For each={items()}><For each={items()} key={...}>— usebynotkey(JSX reserveskeyfor VNode reconciliation){items().map(...)}— use<For>for reactive list rendering;.map()re-creates all DOM nodes on every change<For each={items()} by={index}>— using array index as key defeats the reconciler; use a stable identity likeitem.id
See also: Show · mapArray
Suspense component
<Suspense fallback={loadingUI}>{children}</Suspense>Async boundary that shows fallback while any lazy() component or async child inside is loading. SSR mode streams the fallback immediately and swaps in the resolved content when ready (30s timeout). Nested Suspense boundaries are independent — an inner boundary resolving does not affect the outer.
Example
const LazyPage = lazy(() => import("./HeavyPage"))
<Suspense fallback={<div>Loading...</div>}>
<LazyPage />
</Suspense>See also: lazy · ErrorBoundary
ErrorBoundary component
<ErrorBoundary fallback={(err, reset) => VNodeChild}>{children}</ErrorBoundary>Catches render errors thrown by descendant components. The fallback receives the caught error (typed unknown) and a reset() function — calling reset() clears the error and re-renders children. Without an ErrorBoundary, uncaught errors propagate to the nearest registerErrorHandler or crash the app. There is no onCatch prop — for logging/telemetry, log inside fallback or use registerErrorHandler.
Example
<ErrorBoundary
fallback={(err, reset) => (
<div>
<p>Error: {String(err)}</p>
<button onClick={reset}>Retry</button>
</div>
)}
>
<App />
</ErrorBoundary>Common mistakes
Passing an
onCatchprop — it does not exist.fallbackis the only prop (besides children); log the error inside it or viaregisterErrorHandlerReading
err.messagedirectly —erris typedunknown; narrow it (err instanceof Error ? err.message : String(err)) orString(err)
See also: Suspense · registerErrorHandler
lazy function
lazy(loader: () => Promise<{ default: ComponentFn }>, options?: LazyOptions): LazyComponentWrap a dynamic import for code splitting. Returns a component that integrates with Suspense — the parent Suspense boundary shows its fallback until the import resolves. The loaded component is cached after first resolution.
Example
const Settings = lazy(() => import("./pages/Settings"))
// Use in JSX (wrap with Suspense):
<Suspense fallback={<Spinner />}>
<Settings />
</Suspense>See also: Suspense · Dynamic
Dynamic component
<Dynamic component={comp} {...props} />Renders a component by reference or string tag name. Useful when the component to render is determined at runtime (tab panels, plugin systems, polymorphic containers). When component changes, the previous component unmounts and the new one mounts.
Example
const components = { home: HomePage, about: AboutPage }
const current = signal("home")
<Dynamic component={components[current()]} />See also: lazy · h
cx function
cx(value: ClassValue): stringCombine a class value into a single string. Takes ONE ClassValue — a string, boolean (falsy ignored), object ({ active: true }), or (possibly nested) array. To combine multiple values, pass them as an ARRAY (cx(["btn", active && "on"])), not as separate arguments — cx is single-arg. The class prop on JSX elements already accepts ClassValue directly, so explicit cx() is only needed when building class strings outside JSX.
Example
cx(["foo", "bar"]) // "foo bar"
cx(["base", isActive && "active"]) // conditional
cx({ base: true, active: isActive() }) // object syntax
cx(["a", ["b", { c: true }]]) // nested arrays
// class prop accepts ClassValue directly:
<div class={["base", cond && "active"]} />Common mistakes
Calling
cx("a", "b")with multiple arguments —cxtakes ONEClassValue. Wrap in an array:cx(["a", "b"])
See also: splitProps · mergeProps
useControllableState function
useControllableState<T>(options: { value: () => T | undefined; defaultValue: T; onChange?: (value: T) => void }): [() => T, (next: T | ((prev: T) => T)) => void]The controlled/uncontrolled state pattern, as one primitive. Returns a [getter, setter] pair that reads an external value prop when one is supplied and falls back to internal signal state when it is not, calling onChange on every write either way. value MUST be a getter (() => own.checked) so the controlled prop is read lazily inside reactive scopes — an eager read captures it once and the component stops tracking the owner. Every component with a checked/value/open-style prop needs this; it lives beside splitProps because it is a props primitive, not a hook (it owns no lifecycle). Re-exported from @pyreon/hooks for back-compat.
Example
const Switch = (props: { checked?: boolean; onChange?: (v: boolean) => void }) => {
const [own, rest] = splitProps(props, ["checked", "onChange"])
const [checked, setChecked] = useControllableState({
value: () => own.checked,
defaultValue: false,
onChange: own.onChange,
})
return <button {...rest} aria-checked={() => (checked() ? "true" : "false")} onClick={() => setChecked(!checked())} />
}Common mistakes
Passing a VALUE instead of a getter —
value: own.checkedreads once at setup and freezes; it must bevalue: () => own.checkedHand-rolling
const isControlled = props.value !== undefined+ a parallel signal — that is exactly this primitive, and the hand-rolled version usually forgets to callonChangein the uncontrolled branchWriting to the setter and expecting a CONTROLLED component to update itself — it will not; the owner decides. The setter only reports via
onChangeAssuming
defaultValuestill applies oncevalueis supplied — controlled wins for the whole lifetime of the component
See also: splitProps · mergeProps
splitProps function
splitProps<T, K extends keyof T>(props: T, keys: K[]): [Pick<T, K>, Omit<T, K>]Split a props object into two parts: the picked keys and the rest. Both halves preserve signal reactivity — reads through either half still track the original reactive prop getters. This is the Pyreon replacement for const { x, ...rest } = props destructuring, which captures values once and loses reactivity.
Example
const Button = (props: { class?: string; onClick: () => void; children: VNodeChild }) => {
const [local, rest] = splitProps(props, ["class"])
return <button {...rest} class={cx(["btn", local.class])} />
}Common mistakes
const { class: cls, ...rest } = props— destructuring captures once, loses reactivity. UsesplitProps(props, ["class"])Passing a non-props object —
splitPropsrelies on reactive getter descriptors that the compiler creates on props objectsForgetting that symbol-keyed props are preserved —
splitPropsusesReflect.ownKeysso symbols (likeREACTIVE_PROP) survive
See also: mergeProps · cx
mergeProps function
mergeProps<T extends object[]>(...sources: T): MergedProps<T>Merge multiple props objects with last-source-wins semantics. Reads are lazy — the merged object delegates to the source objects via getters, so signal reactivity is preserved. Commonly used to inject default props: mergeProps({ size: "md" }, props). Forces configurable: true on copied descriptors to prevent "Cannot redefine property" errors.
Example
const Button = (props: { size?: string; variant?: string }) => {
const merged = mergeProps({ size: "md", variant: "primary" }, props)
return <button class={`btn-${merged.size} btn-${merged.variant}`} />
}Common mistakes
Object.assign({}, defaults, props)— loses reactivity. UsemergeProps(defaults, props)insteadmergeProps(props, defaults)— wrong order. Defaults go FIRST, actual props last (last source wins)
See also: splitProps · cx
removeUndefinedProps function
removeUndefinedProps<T>(props: T): { [K in keyof T as T[K] extends undefined ? never : K]: T[K] }Copy a props object, dropping keys whose DATA value is exactly undefined while preserving every getter-shaped (reactive) prop verbatim. The descriptor-aware filter a prop-forwarding HOC runs before mergeProps: an undefined consumer prop must not shadow a default, but a compiler-emitted reactive prop must survive with its subscription intact. Copies property descriptors (never values) — a value-copy would fire each getter at setup time and collapse the live signal to a static snapshot. null / 0 / "" / false are kept; only undefined data props are dropped, and getter descriptors are always kept (cannot peek without firing).
Example
const filtered = removeUndefinedProps(props) // undefined keys gone, getters live
const merged = mergeProps(defaults, filtered)Common mistakes
result[key] = props[key]to filter — fires getter-shaped reactive props, collapsing the subscription. Use this helper (it copies descriptors)Expecting
null/0/falseto be dropped — onlyundefineddata values are removedCalling on
undefined—Object.getOwnPropertyDescriptors(undefined)throws; guard the input
See also: mergeProps · splitProps · makeReactiveProps
createUniqueId function
createUniqueId(): stringGenerate a unique string ID ("pyreon-1", "pyreon-2", ...) that is consistent between server and client when called in the same order. SSR-safe — the counter resets per request context. Use for id/for/aria-* attribute pairing in components.
Example
const LabeledInput = (props: { label: string }) => {
const id = createUniqueId()
return (
<>
<label for={id}>{props.label}</label>
<input id={id} />
</>
)
}See also: splitProps
Portal component
<Portal target={element}>{children}</Portal>Render children into a DOM element outside the component tree (typically document.body). Useful for modals, tooltips, and overlays that need to escape parent overflow/z-index stacking contexts. Context values from the Portal source tree are preserved.
Example
<Portal target={document.body}>
<div class="modal-overlay">
<div class="modal">Content</div>
</div>
</Portal>See also: Dynamic
mapArray function
mapArray<T, U>(source: () => T[], getKey: (item: T) => string | number, map: (item: T) => U): () => U[]Low-level reactive array mapping used internally by <For>. Maps a reactive array through a transform, caching results per KEY so unchanged items reuse their mapped value. Takes THREE args — the source accessor, a getKey identity function, and the map transform. Prefer <For> in JSX — use mapArray only when you need a reactive derived array outside of rendering.
Example
const items = signal([{ id: 1, n: 2 }, { id: 2, n: 3 }])
const doubled = mapArray(() => items(), (item) => item.id, (item) => item.n * 2)
// doubled() → [4, 6] — updates reactively, keyed by idCommon mistakes
Omitting the
getKeyargument —mapArrayrequires 3 args (source, getKey, map); without a key function it cannot cache per-item across updates
See also: For
createRef function
createRef<T>(): Ref<T>Create a mutable ref object ({ current: T | null }) for holding DOM element references. Pass as the ref prop on JSX elements — the runtime sets .current after mount and clears it on unmount. Callback refs ((el: T | null) => void) are also supported via RefProp<T>.
Example
const inputRef = createRef<HTMLInputElement>()
onMount(() => inputRef.current?.focus())
return <input ref={inputRef} />See also: onMount
nativeCompat function
<T>(fn: T) => TMark a Pyreon framework component as "self-managing" so compat layers (@pyreon/{react,preact,vue,solid}-compat) skip their wrapping and route the component through Pyreon's mount path. Use on every @pyreon/* JSX component whose setup body uses provide() / lifecycle hooks / signal subscriptions — wrapping breaks those by running the body inside the compat layer's render context instead of Pyreon's. Idempotent; non-function inputs pass through unchanged. The marker is a registry symbol (Symbol.for("pyreon:native-compat")), so framework and compat sides share it without an import dependency between them.
Example
// In a framework package:
export const RouterView = nativeCompat(function RouterView(props) {
provide(RouterContext, ...)
return <div>{children}</div>
})Common mistakes
Forgetting to mark a new framework JSX export — under compat mode, the component's
provide()/onMount()calls fail with "called outside component setup" warnings and the rendered DOM silently breaks.Marking user-app components — only
@pyreon/*framework components that already manage their own reactivity should be marked. User components in compat mode are SUPPOSED to be wrapped (that's how they get re-render-on-state-change semantics).
See also: isNativeCompat · NATIVE_COMPAT_MARKER
isNativeCompat function
(fn: unknown) => booleanCompat-layer-side: read whether a function has been marked as a Pyreon native framework component via nativeCompat(). Compat jsx() calls this to decide whether to skip the React/Vue/Solid/Preact-style wrapping. Always returns false for non-function inputs.
Example
// In a compat layer's jsx-runtime:
if (isNativeCompat(type)) return h(type, props)
return wrapCompatComponent(type)(props)See also: nativeCompat · NATIVE_COMPAT_MARKER
NATIVE_COMPAT_MARKER constant
symbolThe well-known registry symbol (Symbol.for("pyreon:native-compat")) used to mark a component as a Pyreon native framework component. Most callers should use nativeCompat() / isNativeCompat() instead of touching the symbol directly; exported for advanced cases (e.g., a compat layer that wants to inspect the property without going through the helper).
Example
import { NATIVE_COMPAT_MARKER } from '@pyreon/core'
// Equivalent to nativeCompat(MyComponent):
;(MyComponent as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER] = trueSee also: nativeCompat · isNativeCompat
ExtractProps type
type ExtractProps<T> = /* matches up to 4 overloads, unions the props */ T extends ComponentFn<infer P> ? P : TExtracts the props type from a ComponentFn. Passes through unchanged if T is not a ComponentFn. Multi-overload aware — matches up to 4 call signatures and produces the UNION of their first-argument types. Critical for multi-overload primitives (Iterator, List, Element) whose loosest overload is last; without overload-aware extraction, HOC wrapping (rocketstyle(), attrs()) silently downgraded their public prop surface. Single-overload functions still work — the union of 4 copies of the same props type dedupes back to the single shape.
Example
function Iterator<T extends SimpleValue>(p: { data: T[]; valueName?: string }): VNodeChild
function Iterator<T extends ObjectValue>(p: { data: T[]; component: ComponentFn<T> }): VNodeChild
type Props = ExtractProps<typeof Iterator>
// → { data: SimpleValue[]; valueName?: string }
// | { data: ObjectValue[]; component: ComponentFn<ObjectValue> }Common mistakes
Assuming
ExtractProps<T>returns only the LAST overload — pre-fix it did, post-fix it returns the UNION of up to 4 overloads. Functions with more than 4 overloads still drop the extras.Using
T extends (props: infer P) => any ? P : neverdirectly in user code — that pattern captures only the LAST overload of a multi-overload function. UseExtractProps<T>to get the full union.
See also: HigherOrderComponent
HigherOrderComponent type
type HigherOrderComponent<HOP, P> = ComponentFn<HOP & P>Typed HOC pattern where HOP is the props the HOC adds and P is the wrapped component's own props. The resulting component accepts both sets of props.
Example
function withLogger<P>(Wrapped: ComponentFn<P>): HigherOrderComponent<{ logLevel?: string }, P> {
return (props) => {
console.log(`[${props.logLevel ?? "info"}] Rendering`)
return <Wrapped {...props} />
}
}See also: ExtractProps
Package-level notes
Components run once: Pyreon components are plain functions that execute a single time. Reactivity comes from reading signals inside reactive scopes (JSX expression thunks,
effect(),computed()), not from re-running the component function.if (!cond()) return nullat the top level runs once and is static — usereturn (() => { if (!cond()) return null; return <div /> })for reactive conditional rendering.
Destructuring props kills reactivity:
const { name } = propscaptures the value at setup time — it becomes static. Useprops.nameinside reactive scopes, orsplitProps(props, ["name"])for rest patterns. The compiler handlesconst x = props.y; return <div>{x}</div>by inliningprops.yback at the use site, but only forconst(notlet/var).
Two context types:
createContext<T>returnsTfromuseContext()— safe to destructure.createReactiveContext<T>returns() => T— must call to read. Using the wrong one is a common source of stale-value bugs (static context for dynamic values) or unnecessary ceremony (reactive context for constants).
For uses by, not key: The
<For>component uses thebyprop for its key function because JSX extractskeyas a special VNode reconciliation prop. Writing<For each={items()} key={fn}>silently passes the key to the VNode system instead of the list reconciler.
JSX uses standard HTML attributes: Use
classnotclassName,fornothtmlFor,onInputnotonChangefor per-keystroke updates. Pyreon maps to native DOM events, not the React synthetic event system.