Askr
Fundamentals

Core Concepts

Understand components, state, scopes, lifecycle work, and deterministic rendering.

  • @askrjs/askr0.0.59

How to use core concepts

Put reactive state inside the component that owns the interaction and derive display values from it during render.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep core concepts configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { derive, state } from '@askrjs/askr';

export function Quantity() {
  const count = state(1);
  const label = derive(() => 'Quantity: ' + count());

  return <button onClick={() => count.set(count() + 1)}>{label()}</button>;
}

Components are functions

An Askr component is a plain function that runs during render and returns JSX. There's no class, no `this`, and no separate render method to override — `function Counter() { const [count, setCount] = state(0); return <button onClick={() => setCount(v => v + 1)}>{count()}</button>; }` is a complete component. Because a component is just a function, testing one is calling it with props and checking what comes back.

State is explicit

State cells are created with `state(initialValue)`, which returns a tuple-like handle: call it as a function to read the current value, call `.set()` (or destructure the second tuple slot) to write a new one. Reading `count()` inside a component subscribes that render to future changes, so nothing re-renders unless it actually read a stale signal. `state()` must run during a component's render call — it captures the owning component instance from context, so calling it at module scope throws.

Routes compose the application

An Askr app isn't one big tree rendered from a single root — it's a set of routes registered with `route()` and served through a manifest, each one mapping a path to the component that owns it. Layouts and groups nest naturally because a route's component can render further routed children, so shared chrome only has to be written once. This keeps navigation, code-splitting, and server rendering all working off the same route definitions.

Rendering stays deterministic

Given the same props and state, a component must produce the same output every time it's called — the runtime relies on this to diff and patch the DOM correctly and to make server-rendered markup match what the client hydrates. Side effects like fetches, timers, or DOM reads belong in lifecycle helpers (`resource`, `task`, `on`), not directly in the function body. Breaking purity shows up as hydration mismatches or inconsistent re-renders, which is why the runtime treats it as a hard rule rather than a suggestion.