Python API¶
Elements and components¶
@component wraps a Python function. Its arguments are props. h(Component,
*children, **props) or Component(*children, **props) creates an element; it
does not call the function eagerly. Python components receive original Python
objects as props. Treat props and state as immutable snapshots.
html.div, html.button, svg.circle, or html("my-element", ...) cover all
DOM tag names. Child values may be elements, text, numbers, booleans/None,
lists/tuples or generators. A bare Python dict is not a valid child.
DOM prop conventions:
| Python | React/browser |
|---|---|
class_, class_name |
className |
html_for, for_ |
htmlFor |
on_click, on_key_down |
onClick, onKeyDown |
aria_label, data_testid |
aria-label, data-testid |
style={"background_color": "red"} |
style={{backgroundColor: "red"}} |
Custom Python prop names are preserved, not automatically camel-cased.
fragment, strict_mode, suspense, activity, view_transition, profiler
adapt the corresponding built-in React components. memo(Component,
compare=None), lazy(loader), forward_ref, create_context(default),
Context.provide(*children, value=...), create_portal are also available.
error_boundary(*children, fallback=..., reset_key=...) isolates render errors.
Its fallback may be an element or a callback receiving the JS error. Changing
reset_key resets it. Event-handler errors are not render errors and are not
caught by a React error boundary.
is_valid_element, clone_element, children_count, children_to_array,
map_children expose common element utilities. map_children callbacks receive
(child, index).
Hooks¶
| Python | React |
|---|---|
use_state(initial) |
useState |
use_reducer(reducer, initial, initializer=None) |
useReducer |
use_context(context) |
useContext |
use_ref(initial=None) |
useRef |
use_effect(effect, dependencies=None) |
useEffect |
use_layout_effect(effect, dependencies=None) |
useLayoutEffect |
use_insertion_effect(effect, dependencies=None) |
useInsertionEffect |
use_memo(factory, dependencies) |
useMemo |
use_callback(fn, dependencies) |
useCallback |
use_id() |
useId |
use_imperative_handle(ref, factory, dependencies=None) |
useImperativeHandle |
use_sync_external_store(subscribe, get_snapshot, get_server_snapshot=None) |
useSyncExternalStore |
use_transition() |
useTransition |
use_deferred_value(value, initial=...) |
useDeferredValue |
use_optimistic(value, reducer=None) |
useOptimistic |
use_action_state(action, initial, permalink=None) |
useActionState |
use_effect_event(fn) |
useEffectEvent |
use_debug_value(value, formatter=None) |
useDebugValue |
use(promise_or_context) |
use |
use_form_status() |
React DOM useFormStatus |
Return tuples follow React: state/setter, state/dispatch, pending/start, or
state/action/pending. A setter accepts a value or functional updater. Refs have
a mutable .current. Callback wrappers preserve React identity in dependency
arrays. start_transition(fn) is available outside hooks.
Dependencies are compared by React's Object.is, with Python object identity
preserved through boxes. None means no dependency array (run every commit),
[] means an empty array (mount/unmount). Effects must return None or a cleanup
function, not a coroutine. For example:
Effect events should only be invoked from effects, as in React. There is no Python static linter enforcing that rule in this release.
Async data and actions¶
import asyncio
from pyreact_forge import component, html, resource, suspense
async def load(key):
await asyncio.sleep(0.1)
return {"message": "resolved"}
messages = resource(load) # Create a stable resource outside the render.
@component
def Content():
data = messages.read("example")
return html.p(data["message"])
@component
def App():
return suspense(Content(), fallback=html.p("Loading…"))
Resource.preload(key) returns a stable promise holder, .read(key) suspends,
.invalidate(key) evicts one result, and .clear() evicts all results. Keys
must be hashable. Invalidation does not itself schedule a React render. Rejected
resources reach the nearest error boundary.
For optimistic transitions, do the optimistic update synchronously in the
action before returning an awaitable. Python async functions start later in the
event loop, so updates inside the coroutine do not automatically retain the
synchronous transition context. Wrap updates after awaits in start_transition
as React's async transition rules require. examples/conformance/app.py contains
the tested pattern.
Class components¶
from pyreact_forge import ClassComponent, class_component, html
@class_component
class Counter(ClassComponent):
def __init__(self, props):
super().__init__(props)
self.state = {"count": 0}
def render(self):
return html.button(
self.state["count"],
on_click=lambda: self.set_state(
lambda previous: {"count": previous["count"] + 1}
),
)
State is a dictionary. set_state merges a partial dictionary; force_update
is available. Supported lifecycle adapter names are component_did_mount,
component_did_update, component_will_unmount, component_did_catch,
should_component_update, get_snapshot_before_update, and static
get_derived_state_from_props / get_derived_state_from_error. PureComponent
uses the React pure base class. Legacy UNSAFE_* lifecycles are not adapted.
Class refs currently expose the JS adapter rather than transparently returning
the Python instance.
Roots, DOM APIs and routing¶
create_root(container="root"), hydrate_root(container, node),
Root.render(node), Root.unmount(), and mount(node, strict=False) adapt the
client renderer. The optional identifier_prefix must match server/client
settings. flush_sync(fn) invokes React DOM's synchronous flush.
preload, preload_module, preinit, preinit_module, preconnect,
prefetch_dns and request_form_reset expose DOM helpers. Their keyword options
are JS option names (for example crossOrigin); use **{"as": "style"} where a
JS option collides with a Python keyword.
cache, cache_signal and add_transition_type are thin upstream adapters.
Server-component cache semantics are not provided by the classic SSR CLI.
runtime_capabilities() returns the bundled React exports.
router.Link, router.Router, router.navigate, router.use_location, and
router.match_path are optional. Route patterns support literal paths,
:parameters and trailing *. Query/hash are preserved in locations and ignored
when matching route paths. This is a small router, not React Router parity.