Reactivity
wybthon.reactivity¶
reactivity
¶
Signal-based reactive primitives with an ownership tree.
This module is the heart of Wybthon's SolidJS-inspired reactivity. It implements a synchronous, glitch-free, pull-based reactive graph that matches SolidJS's observable semantics:
- Writing a signal synchronously propagates to dependents before the
setter returns (outside an explicit :func:
batch). - Memos are lazy: they recompute only when read after a dependency changed, and they skip notifying their own observers when the recomputed value is unchanged (the glitch-free "equality short-circuit").
- Effects run in a second phase after pure computations have settled, so an effect never observes a half-updated graph.
Every reactive computation (effect, memo) is also owned by a parent scope. When that scope re-runs or is disposed, all child computations are torn down automatically, preventing leaks and giving lifecycle semantics that match component mount/unmount boundaries.
Core types:
Signal: mutable reactive container.Owner: base ownership scope (cleanups + children).Computation: a reactive computation (effect or memo) that is itself an ownership scope.Resource: async data wrapper withdata/error/loading.
Public primitives:
create_signal: create a(getter, setter)pair.create_effect: auto-tracking side-effect.create_memo: auto-tracking derived value.on_mount/on_cleanup: lifecycle hooks.batch/untrack: scheduling control.create_root: spawn an independent reactive root.merge_props/split_props: prop composition helpers.map_array/index_array/create_selector: reactive list primitives.
Example
A counter with a derived doubled value::
from wybthon import create_signal, create_memo, create_effect
count, set_count = create_signal(0)
doubled = create_memo(lambda: count() * 2)
create_effect(lambda: print("doubled:", doubled()))
set_count(2) # synchronously prints "doubled: 4"
Classes:
| Name | Description |
|---|---|
Owner |
Base reactive ownership scope. |
Computation |
Reactive computation that tracks sources and re-runs when they change. |
Signal |
Mutable reactive container that notifies observers on change. |
ReactiveProps |
Reactive proxy over a component's props dict. |
Resource |
Async data accessor with reactive loading/error state. |
Functions:
| Name | Description |
|---|---|
batch |
Batch signal updates so dependent effects flush once at the end. |
untrack |
Run |
create_resource |
Create an async |
get_owner |
Return the current reactive owner scope, if any. |
run_with_owner |
Run |
create_signal |
Create a reactive signal and return |
create_effect |
Create an auto-tracking reactive effect. |
create_render_effect |
Create an effect that runs in the render phase, before user effects. |
create_computed |
Create an eagerly-run computation in the pure (pre-render) phase. |
create_reaction |
Create an on-change reaction with manual tracking. |
create_unique_id |
Return a process-unique id string (e.g., for |
catch_error |
Run |
on_error |
Register an error handler on the current reactive owner scope. |
create_deferred |
Return a getter that trails |
create_memo |
Create an auto-tracking computed value and return its getter. |
on_mount |
Register a callback to run once after the component mounts. |
on_cleanup |
Register a cleanup callback on the active reactive owner. |
children |
Resolve and memoize reactive children, returning a memo getter. |
get_props |
Return the |
on |
Create an effect with explicit dependencies. |
create_root |
Run |
merge_props |
Merge multiple prop sources into a reactive proxy. |
split_props |
Split a props source into groups by key name, plus a rest group. |
map_array |
Map a reactive list with stable per-item scopes (keyed by identity). |
index_array |
Map a reactive list with stable per-index scopes. |
create_selector |
Create an |
Owner
¶
Base reactive ownership scope.
Tracks child owners and cleanup callbacks. When disposed, children are disposed first (depth-first), then this owner's own cleanups run. This mirrors SolidJS's ownership model.
Attributes:
| Name | Type | Description |
|---|---|---|
_parent |
Optional[Owner]
|
Parent |
_children |
Optional[Dict[int, Owner]]
|
Child owners, keyed by |
_cleanups |
Optional[List[Callable[[], Any]]]
|
Callbacks invoked LIFO during disposal. Allocated lazily on the first registration. |
_disposed |
bool
|
True after |
_context_map |
Optional[Dict[Any, Any]]
|
Lazily-allocated dict of context values stored at
this owner (used by |
_error_handler |
Optional[Callable[..., Any]]
|
Optional callback receiving exceptions raised by
descendant computations (installed by |
Methods:
| Name | Description |
|---|---|
dispose |
Tear down this owner and all descendants. |
dispose
¶
Tear down this owner and all descendants.
Disposes children depth-first, then runs this owner's cleanup callbacks in LIFO order, and finally detaches from its parent. Subsequent calls are no-ops.
Computation
¶
Computation(fn: Callable[[], Any], *, is_effect: bool = False, is_render: bool = False, is_pure: bool = False, is_memo: bool = False, value: Any = None, equals: Any = _DEFAULT_EQUALS)
Bases: Owner
Reactive computation that tracks sources and re-runs when they change.
A computation is either an effect (run for its side effects) or a memo (a lazily-recomputed derived value that other computations can observe). It is also an ownership scope: child computations created during execution are disposed before each re-run, preventing leaks from conditionally-created effects.
Attributes:
| Name | Type | Description |
|---|---|---|
_fn |
The callback executed by |
|
_sources |
Optional[Dict[Any, None]]
|
Sources (signals or memos) read during the last run. An insertion-ordered dict used as an ordered set, allocated lazily on the first tracked read. |
_state |
int
|
One of |
_is_effect |
True for effects (scheduled in the effects phase). |
|
_is_memo |
True for memos (carry a value and observers). |
Methods:
| Name | Description |
|---|---|
dispose |
Dispose the computation and unsubscribe from all dependencies. |
dispose
¶
Dispose the computation and unsubscribe from all dependencies.
Removes dependency edges, drops this node as a source for any of its observers, tears down child owners, and runs cleanups.
Signal
¶
Signal(value: T, *, equals: Any = _DEFAULT_EQUALS)
Bases: Generic[T]
Mutable reactive container that notifies observers on change.
Most code uses create_signal which returns a
(getter, setter) tuple instead of exposing Signal instances directly.
This class is part of the public surface so Signal[T] can be used in
type hints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value. |
required |
equals
|
Any
|
Equality policy. See |
_DEFAULT_EQUALS
|
Methods:
| Name | Description |
|---|---|
get |
Return the current value and subscribe the active computation. |
peek |
Return the current value without subscribing the active computation. |
set |
Write a new value and synchronously notify observers if it changed. |
get
¶
Return the current value and subscribe the active computation.
When called inside an effect, memo, or reactive hole, this signal is added to that computation's dependency set so it re-runs on future writes. Outside a tracking context the read is untracked.
Returns:
| Type | Description |
|---|---|
T
|
The current value held by the signal. |
set
¶
Write a new value and synchronously notify observers if it changed.
Equality is determined by the equals policy passed to the
constructor (default: is then ==, with equals=False to
bypass the check entirely). Outside a batch,
dependent memos become readable immediately and effects run before
this call returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The new value to store. |
required |
ReactiveProps
¶
Reactive proxy over a component's props dict.
Every attribute or item access returns a reactive accessor: a
zero-arg callable that returns the current value and tracks the read.
This mirrors SolidJS's props.x semantics, adapted to Python.
Access patterns:
| Expression | Returns | Notes |
|---|---|---|
props.name |
callable getter | Stable across reads. |
props.name() |
current value | Tracked when called inside an effect or hole. |
props["name"] |
callable getter | Same as props.name. |
props.get("name", default) |
callable getter | Returns default when missing. |
props.value("name", default) |
current value | One-shot snapshot, with auto-unwrap. |
Embed the accessor directly in the VNode tree to create an automatic reactive hole; the surrounding DOM region updates only when the prop changes.
Example
Note
ReactiveProps is read-only. Parents and the reconciler update it
via the internal _update method.
Methods:
| Name | Description |
|---|---|
value |
Return the current value for |
get |
Return a callable getter for |
keys |
Return the prop names currently set on this instance. |
values |
Return current prop values as a list (tracked). |
items |
Return |
value
¶
Return the current value for key (tracked, with auto-unwrap).
If the stored prop value is a getter, it's invoked and the result
returned, mirroring _make_getter. If default is provided, it's
returned when key is absent from both the props dict and the
component's parameter defaults.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Prop name. |
required |
default
|
Any
|
Value returned when the key is missing. When omitted,
missing keys yield |
_MISSING
|
Returns:
| Type | Description |
|---|---|
Any
|
The current prop value (auto-unwrapped if it's a getter). |
get
¶
Return a callable getter for key, falling back to default if missing.
When key exists on the prop bag (in raw props, prior signals, or
component-parameter defaults), the returned getter reads the underlying
signal: a tracking scope subscribes to it.
When key is missing, the getter returns default on each call
without creating a tracked signal. This means repeated get(key, x)
/ get(key, y) calls each return their own default (no sticky
behavior). Components that need reactivity for a possibly-missing
prop should declare it as a parameter with a default value;
@component ensures a signal is created up front so future updates
always propagate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Prop name. |
required |
default
|
Any
|
Value returned by the fallback getter when |
None
|
Returns:
| Type | Description |
|---|---|
Callable[[], Any]
|
A zero-arg callable. Subscribers to a missing-key getter are |
Callable[[], Any]
|
not notified when the prop later appears. |
Resource
¶
Resource(fetcher: FetchFn, source: Optional[Callable[[], Any]] = None, *, initial_value: Any = _MISSING)
Bases: Generic[R]
Async data accessor with reactive loading/error state.
Matches SolidJS's resource shape: the resource is the accessor.
Call it to read the current data (tracked); read loading, error,
latest, and state as properties (also tracked).
While a resource is in its initial "pending" state, reading it
under a Suspense boundary automatically
registers it with that boundary; no manual wiring is needed.
Refetches ("refreshing" state) don't re-trigger Suspense; the
previous data stays available, matching SolidJS.
States:
"unresolved": never fetched (source wasNone/False)."pending": first fetch in flight, no data yet."ready": data available."refreshing": refetch in flight, previous data still readable."errored": last fetch raised.
Example
Methods:
| Name | Description |
|---|---|
mutate |
Set the resource's data directly, without fetching. |
refetch |
Cancel any in-flight request and start a new fetch. |
cancel |
Abort the current in-flight fetch, if any. |
Attributes:
| Name | Type | Description |
|---|---|---|
loading |
bool
|
|
error |
Optional[Any]
|
The most recent exception, or |
latest |
Optional[R]
|
The most recent data, even while refreshing (tracked read). |
state |
str
|
The current lifecycle state string (tracked read). |
latest
property
¶
latest: Optional[R]
The most recent data, even while refreshing (tracked read).
Unlike calling the resource, reading latest never registers
with a Suspense boundary.
mutate
¶
Set the resource's data directly, without fetching.
Supports functional updates like a signal setter. Clears any
error and marks the resource "ready".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Union[R, Callable[[Optional[R]], R]]
|
The new data, or a callable receiving the current data. |
required |
Returns:
| Type | Description |
|---|---|
Optional[R]
|
The stored value. |
refetch
¶
Cancel any in-flight request and start a new fetch.
The resource enters "pending" (first fetch) or "refreshing"
(data already present). Older in-flight tasks are ignored when
they resolve.
cancel
¶
Abort the current in-flight fetch, if any.
Calls AbortController.abort() on the wrapped browser controller,
cancels the asyncio task, and resets loading to False without
touching the data or error state.
signal
¶
signal(value: T) -> Signal[T]
Create a new Signal with the given initial value.
Most code should call create_signal instead;
this function exists for low-level uses that need the raw Signal
object (for example, attaching custom subscribers).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
The initial value stored in the signal. |
required |
Returns:
| Type | Description |
|---|---|
Signal[T]
|
A new |
computed
¶
computed(fn: Callable[[], T]) -> _Computed[T]
Create a _Computed value derived from other signals.
Low-level helper used by create_memo. Most
code should use create_memo directly because it returns a plain
getter (matching SolidJS's API).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], T]
|
Zero-arg callable. Re-evaluated when any signal it reads changes. |
required |
Returns:
| Type | Description |
|---|---|
_Computed[T]
|
A |
effect
¶
effect(fn: Callable[[], Any]) -> Computation
Run a reactive effect immediately and return its Computation.
Low-level helper. Most code should use
create_effect which additionally supports
receiving the previous return value as an argument.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], Any]
|
Zero-arg callback. Re-runs when any signal it reads changes. |
required |
Returns:
| Type | Description |
|---|---|
Computation
|
The underlying |
on_effect_cleanup
¶
on_effect_cleanup(comp: Computation, fn: Callable[[], Any]) -> None
Register fn to run when comp is disposed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
comp
|
Computation
|
The computation whose disposal should trigger the cleanup. |
required |
fn
|
Callable[[], Any]
|
Zero-arg cleanup callback. |
required |
batch
¶
Batch signal updates so dependent effects flush once at the end.
Two call shapes are supported:
- Context manager (Pythonic):
- Callback (SolidJS style):
When called with a function, the function's return value is returned. Effects are flushed synchronously when the outermost batch exits, matching SolidJS semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Optional[Callable[[], T]]
|
Optional zero-arg callable. When omitted, returns a context manager. |
None
|
Returns:
| Type | Description |
|---|---|
Union[T, _Batch]
|
Either a |
Union[T, _Batch]
|
return value of |
untrack
¶
untrack(fn: Callable[[], T]) -> T
Run fn without tracking any signal reads.
Useful inside effects when you need to read a signal without creating a dependency, or during component setup to seed local state from a prop without subscribing.
Inside untrack the dev-mode destructured-prop warning is also
silenced, so count, set_count = create_signal(untrack(initial))
cleanly opts out of the noise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], T]
|
Zero-arg callable to invoke with tracking suppressed. |
required |
Returns:
| Type | Description |
|---|---|
T
|
Whatever |
create_resource
¶
create_resource(source_or_fetcher: Union[Callable[[], Any], Callable[..., Awaitable[R]]], fetcher: Optional[Callable[..., Awaitable[R]]] = None, *, initial_value: Any = _MISSING) -> Resource[R]
Create an async Resource accessor.
Can be called two ways:
create_resource(fetcher): simple fetcher, fetches immediately.create_resource(source, fetcher): fetches whenever thesourcegetter's tracked value changes; when the source yieldsNoneorFalsethe fetch is skipped (the resource stays unresolved).
The fetcher may be sync or async. When it declares a positional
parameter, the current source value is passed as the first argument.
When it accepts a signal keyword argument, an AbortSignal is
passed for cancellation support in the browser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_or_fetcher
|
Union[Callable[[], Any], Callable[..., Awaitable[R]]]
|
When called with one argument, this is the fetcher. When called with two, this is the source getter (typically a signal accessor). |
required |
fetcher
|
Optional[Callable[..., Awaitable[R]]]
|
Optional fetcher. Required when |
None
|
initial_value
|
Any
|
Optional seed data. When provided, the resource
starts |
_MISSING
|
Returns:
| Type | Description |
|---|---|
Resource[R]
|
A |
Resource[R]
|
|
get_owner
¶
Return the current reactive owner scope, if any.
Capture the owner before crossing async boundaries (e.g., await) and
restore it with run_with_owner to maintain
proper lifecycle management.
Returns:
| Type | Description |
|---|---|
Optional[Owner]
|
The active |
Optional[Owner]
|
scope. |
run_with_owner
¶
Run fn under the given ownership scope.
Useful for restoring context after an await boundary where the
reactive owner would otherwise be lost.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
owner
|
Optional[Owner]
|
The owner to install for the duration of |
required |
fn
|
Callable[[], T]
|
Zero-arg callable to execute. |
required |
Returns:
| Type | Description |
|---|---|
T
|
Whatever |
create_signal
¶
Create a reactive signal and return (getter, setter).
Works inside or outside components. Inside a stateful component the signal is captured by the render function's closure and persists naturally; there's no cursor system or "rules of hooks".
The setter supports functional updates, matching SolidJS: when
called with a callable, the callable receives the current value and
its return value becomes the new value. To store a callable as the
signal's value, wrap it: set_fn(lambda _prev: my_callable).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
T
|
Initial value stored in the signal. |
required |
equals
|
Any
|
Equality policy controlling when subscribers are notified. Accepts:
|
_DEFAULT_EQUALS
|
Returns:
| Type | Description |
|---|---|
tuple
|
A |
tuple
|
suitable for embedding as a reactive hole; its |
tuple
|
returns the current value without subscribing the caller. The |
tuple
|
setter returns the value that was stored. |
create_effect
¶
create_effect(fn: Callable[..., Any]) -> Computation
Create an auto-tracking reactive effect.
The effect runs immediately and re-runs whenever any signal read inside
fn changes. on_cleanup may be called inside
fn to register per-run cleanup that runs before re-execution and on
disposal.
If fn accepts a positional parameter, the previous return value
is passed on each re-execution (None on the first run), matching
SolidJS's createEffect(prev => ...).
Inside a component, the effect is automatically disposed on unmount.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., Any]
|
Zero- or one-arg callable. When it accepts an argument, the previous return value is forwarded. |
required |
Returns:
| Type | Description |
|---|---|
Computation
|
The underlying |
Computation
|
manually. |
create_render_effect
¶
create_render_effect(fn: Callable[..., Any]) -> Computation
Create an effect that runs in the render phase, before user effects.
Matches SolidJS's createRenderEffect: within one flush, all pending
render effects execute before any create_effect
callbacks, and before the batched DOM ops are committed. Wybthon's
internal reactive holes and prop bindings run in this phase, so a
render effect observes the DOM in the same state the framework's own
bindings do (updates emitted but not yet committed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., Any]
|
Zero- or one-arg callable (the previous return value is
forwarded when accepted, as with |
required |
Returns:
| Type | Description |
|---|---|
Computation
|
The underlying |
create_computed
¶
create_computed(fn: Callable[..., Any]) -> Computation
Create an eagerly-run computation in the pure (pre-render) phase.
Matches SolidJS's createComputed: within one flush, all pending
create_computed computations run before any render effects,
so signals they write are fully settled before the DOM updates.
Prefer create_memo for plain derived
values; reach for create_computed only when you must push a value
into another signal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., Any]
|
Zero- or one-arg callable. |
required |
Returns:
| Type | Description |
|---|---|
Computation
|
The underlying |
create_reaction
¶
Create an on-change reaction with manual tracking.
Matches SolidJS's createReaction: the returned track function
runs its argument once with tracking enabled; the first time any
tracked dependency changes, on_invalidate fires (untracked) and
tracking stops until track is called again. Useful for "notify me
once" patterns where the rendering of the change is decoupled from
its detection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_invalidate
|
Callable[[], Any]
|
Zero-arg callback invoked once per tracked change. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Callable[[], Any]], None]
|
A |
Callable[[Callable[[], Any]], None]
|
signal reads should be tracked. |
create_unique_id
¶
create_unique_id() -> str
Return a process-unique id string (e.g., for for/id attribute pairs).
Matches SolidJS's createUniqueId.
Returns:
| Type | Description |
|---|---|
str
|
A short unique string like |
catch_error
¶
Run fn with an error handler, catching errors thrown now or later.
Matches SolidJS's catchError. The handler receives exceptions
raised synchronously by fn and exceptions raised later by any
effect created inside fn (they route up the ownership tree to the
nearest handler).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], T]
|
Zero-arg callable to execute under the error scope. |
required |
handler
|
Callable[[Any], Any]
|
Callback receiving the exception. |
required |
Returns:
| Type | Description |
|---|---|
Optional[T]
|
The return value of |
on_error
¶
Register an error handler on the current reactive owner scope.
Matches SolidJS's onError: exceptions raised by child computations
(effects created under this scope, reactive holes, descendant
component setups) route to the nearest ancestor handler. Multiple
calls in the same scope chain the handlers; each runs in
registration order.
Unlike catch_error, which wraps a single
function call in its own scope, on_error protects everything
created under the current scope for its remaining lifetime.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[[Any], Any]
|
Callback receiving the exception. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called outside any reactive scope. |
create_deferred
¶
Return a getter that trails source, updating on the next event-loop tick.
A lightweight analogue of SolidJS's createDeferred: reads of the
returned getter don't recompute synchronously when source changes;
instead, the new value is published asynchronously (via the running
asyncio loop when one exists, else immediately). Use it to decouple
expensive consumers from rapid-fire updates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Callable[[], T]
|
Zero-arg tracked getter. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], T]
|
A zero-arg getter for the deferred value. |
create_memo
¶
Create an auto-tracking computed value and return its getter.
Re-computes lazily, only when read after a tracked source changed. Inside a component, the underlying computation is disposed on unmount.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], T]
|
Zero-arg callable producing the derived value. |
required |
equals
|
Any
|
Equality policy controlling when the memo's own
observers are notified after a recompute. Same semantics as
|
_DEFAULT_EQUALS
|
Returns:
| Type | Description |
|---|---|
Callable[[], T]
|
A zero-arg callable. Reading it inside a tracking scope creates |
Callable[[], T]
|
a dependency on the memoised value. Its |
Callable[[], T]
|
the (up-to-date) value without subscribing the caller. |
on_mount
¶
Register a callback to run once after the component mounts.
Must be called during a component's setup phase (the body of a
@component function, before the return).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], Any]
|
Zero-arg callback invoked once after the first render commits. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called outside a component setup phase. |
on_cleanup
¶
Register a cleanup callback on the active reactive owner.
Lifecycle differs by call site:
- Inside
create_effect: runs before each re-execution and on final disposal. - Inside a component's setup phase: runs when the component unmounts.
- Inside a reactive hole: runs before each hole re-evaluation and when the hole is disposed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], Any]
|
Zero-arg cleanup callback. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called outside any reactive scope. |
children
¶
Resolve and memoize reactive children, returning a memo getter.
Wraps a getter that returns children (e.g., lambda: props.children())
and returns a memo that flattens nested lists and unwraps callables.
Matches SolidJS's children() helper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[], Any]
|
Zero-arg getter that returns the raw children value
(typically |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], List[Any]]
|
A zero-arg memo getter producing a flat list of resolved children. |
get_props
¶
Return the ReactiveProps proxy for the current component.
The returned object provides reactive access to individual props. Each attribute / index lookup yields a callable getter; call the getter to read the current value (tracked when called inside an effect or hole).
Most components should rely on the destructured parameters provided
by @component. Use get_props() for advanced cases such as generic
wrappers, key iteration, or proxy-mode interop.
Returns:
| Type | Description |
|---|---|
'ReactiveProps'
|
The cached |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called outside a component setup phase. |
on
¶
on(deps: Union[Callable[[], Any], List[Callable[[], Any]]], fn: Callable[..., Any], defer: bool = False) -> Computation
Create an effect with explicit dependencies.
deps may be a single getter or a list of getters. fn receives the
current value(s) as positional arguments. Only the listed deps are
tracked; the body of fn runs inside untrack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deps
|
Union[Callable[[], Any], List[Callable[[], Any]]]
|
One getter or a list of getters to subscribe to. |
required |
fn
|
Callable[..., Any]
|
Callback receiving the current dep value(s) on each change. |
required |
defer
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
Computation
|
The underlying |
create_root
¶
Run fn inside an independent reactive root.
Useful for spawning long-lived reactive work that shouldn't be tied to the surrounding component's lifecycle (e.g., global stores).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Callable[[], None]], T]
|
Callable receiving a |
required |
Returns:
| Type | Description |
|---|---|
T
|
Whatever |
merge_props
¶
merge_props(*sources: Any) -> '_MergedProps'
Merge multiple prop sources into a reactive proxy.
Each source may be a plain dict, a zero-arg getter that returns a
dict, or another _MergedProps / _SplitProps. Later sources
override earlier ones on key conflicts.
The returned object supports dict-like access ([], .get, in,
iteration). When a source is a callable, it's called on each property
access, so signal reads inside the getter are tracked by the current
reactive computation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*sources
|
Any
|
One or more prop sources, in priority order (rightmost wins). |
()
|
Returns:
| Type | Description |
|---|---|
'_MergedProps'
|
A reactive merged-props proxy. |
split_props
¶
Split a props source into groups by key name, plus a rest group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
props
|
Any
|
A |
required |
*key_groups
|
List[str]
|
One or more lists of keys defining each group. |
()
|
Returns:
| Type | Description |
|---|---|
Any
|
A tuple |
...
|
reactive proxy that lazily reads from the original |
Tuple[Any, ...]
|
final |
Tuple[Any, ...]
|
group. |
map_array
¶
map_array(source: Callable[[], Optional[List[Any]]], map_fn: Callable[[Callable[[], Any], Callable[[], int]], T]) -> Callable[[], List[T]]
Map a reactive list with stable per-item scopes (keyed by identity).
Items are matched by reference identity. The mapping callback runs once per unique item; when an item leaves the source list, its reactive scope is disposed automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Callable[[], Optional[List[Any]]]
|
Zero-arg getter that returns the current list (typically a signal accessor). |
required |
map_fn
|
Callable[[Callable[[], Any], Callable[[], int]], T]
|
Called as |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], List[T]]
|
A zero-arg getter producing the mapped list. Reading it inside a |
Callable[[], List[T]]
|
reactive scope subscribes to source changes. |
index_array
¶
index_array(source: Callable[[], Optional[List[Any]]], map_fn: Callable[[Callable[[], Any], int], T]) -> Callable[[], List[T]]
Map a reactive list with stable per-index scopes.
Unlike map_array, scopes are keyed by index
position. Each slot has a reactive item_getter signal that
updates when the value at that index changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Callable[[], Optional[List[Any]]]
|
Zero-arg getter that returns the current list. |
required |
map_fn
|
Callable[[Callable[[], Any], int], T]
|
Called as |
required |
Returns:
| Type | Description |
|---|---|
Callable[[], List[T]]
|
A zero-arg getter producing the mapped list. |
create_selector
¶
Create an O(1) selection signal.
When source() changes, only computations that previously called the
returned is_selected(key) with the old or new key are
notified, instead of every subscriber re-running.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Callable[[], Any]
|
Zero-arg getter returning the currently-selected key. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Any], bool]
|
A function |
Callable[[Any], bool]
|
selection changes. |
Ownership classes¶
Owner¶
Base reactive ownership scope. Tracks child owners and cleanup callbacks.
| Attribute | Description |
|---|---|
_parent |
Parent Owner or None for roots. |
_children |
List of child Owner instances. |
_cleanups |
Callbacks run on disposal (LIFO order). |
_context_map |
Optional dict mapping context IDs to values (used by Provider). |
| Method | Description |
|---|---|
dispose() |
Dispose children depth-first, run own cleanups, detach from parent. |
_lookup_context(ctx_id, default) |
Walk up the owner chain to find a context value. |
_set_context(ctx_id, value) |
Store a context value on this owner. |
Computation(Owner)¶
Reactive computation (an effect or a memo) that tracks its sources (signals and other memos) and re-runs when they change. Also an ownership scope: child computations created during execution are disposed before each re-run.
Each computation carries a state for the push-mark / pull-recompute scheduler:
CLEAN (current), CHECK (a transitive source may have changed), or
DIRTY (a direct source did change). A signal write pushes DIRTY/CHECK
markers out to dependents and queues affected effects; effects then pull
their sources up to date, which keeps updates glitch-free.
| Method | Description |
|---|---|
_stale(state) |
Mark CHECK/DIRTY and propagate a CHECK to observers; queue the node when it's an effect transitioning from CLEAN. |
_update_if_necessary() |
Resolve CHECK by pulling sources, then recompute if DIRTY. Marked CLEAN before recompute so a re-entrant self-write reschedules it. |
_update() |
Dispose children and cleanups, rebuild the dependency set, and re-execute the function under _current_owner = _current_observer = self. For memos, store the new value and escalate observers to DIRTY only when it changed. |
dispose() |
Unsubscribe from all sources, drop self as a source for any observers, dispose children, run cleanups. |
Public API¶
Signals-first API (recommended)¶
create_signal(value, *, equals=...) -> (getter, setter). Optionalequals: default uses value equality (==) with an identity (is) fast-path;equals=Trueis equivalent to the default;equals=Falsenotifies on everyset();equals=fnwithfn(old, new) -> boolskips notification whenfnreturnsTrue(custom comparator). Useequals=lambda a, b: a is bfor SolidJS-style identity-only semantics. The getter exposes.peek()for untracked reads. The setter also accepts an updater function:set_count(lambda n: n + 1)computes the new value from the previous one and returns the value it stored.create_effect(fn) -> Computation. The returnedComputationis added as a child of the current owner. Inside a component's setup phase the owner is the_ComponentContext(effect survives re-renders, disposed on unmount). Inside a render function the owner is the renderComputation(effect disposed on re-render). Supports previous value:create_effect(lambda prev: ...). User effects run after render effects and after the DOM commit in each flush, matching Solid'screateEffecttiming.create_render_effect(fn) -> Computation. Likecreate_effect, but queued in the render phase: it runs before the DOM commit and before user effects in each flush. The framework uses this internally for DOM bindings; use it when an effect must run during the render phase.create_computed(fn) -> Computation. An eager, pure computation that runs before render effects in each flush, mirroring Solid'screateComputed. Prefercreate_memofor derived values; usecreate_computedto push a signal write that later work depends on.create_memo(fn, *, equals=...) -> getter. Creates a lazy memoComputationunder the current owner; recomputes only when read after a source changed, and is disposed when the owner is disposed. Optionalequals(same semantics ascreate_signal) controls when the memo's own observers are notified after a recompute. The getter exposes.peek()for untracked reads.create_reaction(on_invalidate) -> track. On-change reaction with manual tracking, mirroring Solid'screateReaction. Calltrack(fn)to runfnwith tracking; the first change to any tracked dependency fireson_invalidate(untracked) and stops tracking untiltrackis called again.on_error(handler). Registers an error handler on the current reactive owner scope, mirroring Solid'sonError. Exceptions raised by child computations route to the nearest ancestor handler; multiple handlers in one scope chain in registration order.create_deferred(source) -> getter. Returns a read-only getter that trailssource: the new value is published on the next event-loop tick (immediately when no asyncio loop is running) so expensive consumers can lag behind rapid-fire updates, mirroring Solid'screateDeferred.create_unique_id() -> str. Returns a unique, stable ID string ("wyb-0","wyb-1", ...) for wiringfor/aria-*attributes, mirroring Solid'screateUniqueId.catch_error(fn, handler) -> result | None. Runsfnunder a scope whose errors (including errors thrown later by effects created inside it) route tohandlerinstead of propagating. Mirrors Solid'scatchError.on_mount(fn). Run after first render.on_cleanup(fn). Appendsfnto the current owner's cleanup list. Insidecreate_effect: runs before each re-execution and on disposal. Inside a component's setup phase: runs when the component unmounts.batch() -> context managerorbatch(fn) -> result. The callback form flushes synchronously.
create_signal and equals¶
from wybthon import create_signal
# Default: value equality (==) with an identity (is) fast-path.
# Re-setting an unchanged value is a no-op; a new container with
# value-equal contents also skips. Mutating the same list/dict in
# place and re-setting the same reference is a no-op too -- copy
# the container first or pass ``equals=False`` to force notification.
x, set_x = create_signal({"a": 1})
# Equivalent to the default.
y, set_y = create_signal(0, equals=True)
# Always notify subscribers, even when the value is unchanged.
z, set_z = create_signal(0, equals=False)
# SolidJS-style identity-only semantics: notify whenever the new
# reference is not the same Python object as the old.
w, set_w = create_signal([], equals=lambda old, new: old is new)
ReactiveProps and get_props()¶
get_props() returns the ReactiveProps proxy for the current
@component instance. The proxy exposes one consistent shape for
every prop:
props.name(attribute) orprops["name"](item) returns a stable zero-arg accessor. Calling the accessor reads the current value (tracked when called inside an effect or hole). Embedding it in a VNode tree creates a reactive auto-hole.props.value(name, default=None)reads the current value immediately (a one-shot, untracked-friendly snapshot).- The proxy supports
in,len(), iteration, and==against dicts.
from wybthon import component, create_effect, dynamic, get_props, p
@component
def Greeting(name="world"):
props = get_props()
create_effect(lambda: print("name is now", props.name()))
return p(dynamic(lambda: f"Hello, {props.name()}!"))
When a component declares a single positional parameter with no
default, the decorator passes the proxy in directly (proxy mode);
otherwise each parameter is bound to its own accessor and there's no
need to call get_props().
ReactiveProps is read-only; the parent/reconciler updates underlying
values. When a parent passes a getter (e.g., name=my_signal), the
proxy unwraps it transparently, and children always read with props.name().
get_owner() and run_with_owner(owner, fn)¶
After an await, the reactive owner stack may no longer match the component that started the work. Capture the owner before awaiting and restore it when creating effects or other scoped work:
from wybthon import create_effect, get_owner, run_with_owner
async def load():
owner = get_owner()
data = await fetch_something()
run_with_owner(owner, lambda: create_effect(lambda: use(data)))
children(fn)¶
children(getter) wraps a zero-argument callable that returns the children value (often lambda: get_props().children()) and returns a memo getter that flattens and resolves the list. Matches Solid's children() helper. Import under an alias (e.g., from wybthon import children as resolve_children) if your component also names a parameter children.
from wybthon import children, component, dynamic, get_props, h3, section
@component
def Card(title=""):
props = get_props()
resolved = children(lambda: props.children())
return section(h3(dynamic(lambda: props.title())), *resolved(), class_="card")
Resources¶
create_resource(fetcher) -> Resourcecreate_resource(source, fetcher) -> Resource. Refetches when the source changes.- Both forms accept
initial_value=...: the resource starts in the"ready"state with that data, and the first fetch behaves like a refresh (previous data stays readable, Suspense isn't triggered).
A Resource is callable, mirroring Solid's createResource accessor:
res()reads the resolved data (tracked). Reading it inside aSuspenseboundary automatically registers the resource with that boundary while it's pending.res.loadingandres.errorare tracked reads of the loading flag and last error.res.latestreturns the most recent resolved value even while a refetch is in flight (useful for keep-previous UIs).res.stateis one of"unresolved","pending","ready","refreshing", or"errored".res.mutate(value)writes the resolved value directly (optimistic updates);res.refetch()re-runs the fetcher;res.cancel()abandons an in-flight fetch.
from wybthon import Suspense, create_resource, create_signal, h
user_id, set_user_id = create_signal(1)
user = create_resource(user_id, fetch_user) # refetches when user_id changes
view = Suspense(
fallback="Loading...",
children=lambda: h("p", {}, lambda: user()["name"]),
)
Reactive utilities¶
untrack(fn). Run without tracking signal reads.on(deps, fn, defer=False). Effect with explicit deps.create_root(fn). Creates an independentOwnerroot.fnreceives adisposecallback that tears down the root and all its children. Effects created inside the root are owned by it and cleaned up ondispose().merge_props(*sources). Merge prop sources into a reactive proxy. Each source may be a plaindict, a callable getter, or another proxy. Reads are lazy: callable sources are called on each access for signal tracking. Returns an object supporting[],.get(),in,len(), iteration, and==comparison with dicts.split_props(props, *key_groups). Split a props source into reactive proxy groups by key name, plus a rest group. Returns(group1, ..., rest); each proxy lazily reads from the original source.
Reactive list primitives¶
map_array(source, map_fn). Keyed reactive list mapping.sourceis a getter returning a list;map_fn(item_getter, index_getter)runs once per unique item (matched by reference identity). Returns a getter producing the mapped list. Per-item reactive scopes are created and disposed automatically.index_array(source, map_fn). Index-keyed reactive list mapping. Likemap_arraybut keyed by index position.map_fn(item_getter, index: int): the item getter is a signal that updates in place. Returns a getter producing the mapped list.create_selector(source). Efficient selection signal. Returnsis_selected(key) -> bool. When the source changes, only the previous and new key's dependents re-run (O(1) instead of O(n)).
Global state¶
Two module globals drive tracking. _current_owner is the active ownership
scope: effects and memos created while it's set become its children.
_current_observer is the computation currently recording dependencies: a
signal or memo read while it's set subscribes that observer. Computation._update()
sets both to self while it executes, so reads link to the running
computation and newly created scopes are owned by it. Effects flush
synchronously through a shared queue; batch() defers that flush to the
outermost batch boundary.
Type hints are provided for all public functions and classes.