You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/api/pythonnative.md
+11-2Lines changed: 11 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,10 @@
13
13
14
14
Each returns an `Element` descriptor. Visual and layout properties are passed via `style={...}`. See the Component Property Reference for full details.
15
15
16
+
### ErrorBoundary
17
+
18
+
`pythonnative.ErrorBoundary(child, fallback=...)` — catches render errors in *child* and displays *fallback* instead. *fallback* may be an `Element` or a callable that receives the exception and returns an `Element`.
19
+
16
20
### Element
17
21
18
22
`pythonnative.Element` — the descriptor type returned by element functions. You generally don't create these directly.
@@ -23,7 +27,8 @@ Function component primitives:
23
27
24
28
-`pythonnative.component` — decorator to create a function component
25
29
-`pythonnative.use_state(initial)` — local component state
26
-
-`pythonnative.use_effect(effect, deps)` — side effects
30
+
-`pythonnative.use_reducer(reducer, initial_state)` — reducer-based state management; returns `(state, dispatch)`
31
+
-`pythonnative.use_effect(effect, deps)` — side effects, run after native commit
-`pythonnative.use_callback(fn, deps)` — stable function references
@@ -32,6 +37,10 @@ Function component primitives:
32
37
-`pythonnative.create_context(default)` — create a new context
33
38
-`pythonnative.Provider(context, value, child)` — provide a context value
34
39
40
+
### Batching
41
+
42
+
-`pythonnative.batch_updates()` — context manager that batches multiple state updates into a single re-render
43
+
35
44
### Styling
36
45
37
46
-`pythonnative.StyleSheet` — utility for creating and composing style dicts
@@ -53,7 +62,7 @@ Function component primitives:
53
62
54
63
## Reconciler
55
64
56
-
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, and context providers. Used internally by `create_page`.
65
+
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, context providers, and error boundaries. Effects are flushed after each mount/reconcile pass. Used internally by `create_page`.
Copy file name to clipboardExpand all lines: docs/concepts/architecture.md
+18-7Lines changed: 18 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,24 +5,35 @@ PythonNative combines **direct native bindings** with a **declarative reconciler
5
5
## High-level model
6
6
7
7
1.**Declarative element tree:** Your `@pn.component` function returns a tree of `Element` descriptors (similar to React elements / virtual DOM nodes).
8
-
2.**Function components and hooks:** All UI is built with `@pn.component` functions using `use_state`, `use_effect`, `use_navigation`, etc. — inspired by React hooks but designed for Python.
8
+
2.**Function components and hooks:** All UI is built with `@pn.component` functions using `use_state`, `use_reducer`, `use_effect`, `use_navigation`, etc. — inspired by React hooks but designed for Python.
9
9
3.**Reconciler:** On first render, the reconciler walks the tree and creates real native views via the platform backend. On subsequent renders (triggered by hook state changes), it diffs the new tree against the old one and applies the minimal set of native mutations.
10
-
4.**Key-based reconciliation:** Children can be assigned stable `key` values to preserve identity across re-renders — critical for lists and dynamic content.
11
-
5.**Direct bindings:** Under the hood, native views are created and updated through direct platform calls:
10
+
4.**Post-render effects:** Effects queued via `use_effect` are flushed **after** the reconciler commits native mutations, matching React semantics. This guarantees that effect callbacks interact with the committed native tree.
11
+
5.**State batching:** Multiple state updates triggered during a render pass (e.g. from effects) are automatically batched into a single re-render. Explicit batching is available via `pn.batch_updates()`.
12
+
6.**Key-based reconciliation:** Children can be assigned stable `key` values to preserve identity across re-renders — critical for lists and dynamic content.
13
+
7.**Error boundaries:**`pn.ErrorBoundary` catches render errors in child subtrees and displays fallback UI, preventing a single component failure from crashing the entire page.
14
+
8.**Direct bindings:** Under the hood, native views are created and updated through direct platform calls:
-**Android:** Chaquopy exposes Java classes (`android.widget.TextView`, `android.widget.Button`, etc.) via the JNI bridge.
14
-
6.**Thin native bootstrap:** The host app remains native (Android `Activity` or iOS `UIViewController`). It calls `create_page()` internally to bootstrap your Python component, and the reconciler drives the UI from there.
17
+
9.**Thin native bootstrap:** The host app remains native (Android `Activity` or iOS `UIViewController`). It calls `create_page()` internally to bootstrap your Python component, and the reconciler drives the UI from there.
15
18
16
19
## How it works
17
20
18
21
```
19
-
@pn.component fn → Element tree → Reconciler → Native views
22
+
@pn.component fn → Element tree → Reconciler → Native views → Flush effects
The reconciler uses **key-based diffing** (matching children by key first, then by position). When a child with the same key/type is found, its props are updated in-place on the native view. When the type changes, the old native view is destroyed and a new one is created.
25
29
30
+
### Render lifecycle
31
+
32
+
1.**Render phase:** Component functions execute. Hooks record state reads, queue effects, and register memos. No native mutations happen yet.
33
+
2.**Commit phase:** The reconciler applies the diff to native views — creating, updating, and removing views as needed.
34
+
3.**Effect phase:** Pending effects are flushed in depth-first order (children before parents). Cleanup functions from the previous render run before new effect callbacks.
35
+
4.**Drain phase:** If effects set state, a new render pass is automatically triggered and the cycle repeats (up to a safety limit to prevent infinite loops).
36
+
26
37
## Component model
27
38
28
39
PythonNative uses a single component model: **function components** decorated with `@pn.component`.
@@ -40,7 +51,7 @@ def Counter(initial: int = 0):
40
51
41
52
Each component is a Python function that:
42
53
- Accepts props as keyword arguments
43
-
- Uses hooks for state (`use_state`), side effects (`use_effect`), navigation (`use_navigation`), and more
54
+
- Uses hooks for state (`use_state`, `use_reducer`), side effects (`use_effect`), navigation (`use_navigation`), and more
44
55
- Returns an `Element` tree describing the UI
45
56
- Each call site creates an independent instance with its own hook state
The reducer receives the current state and an action, and returns the new state. Actions can be any value (strings, dicts, etc.). The component only re-renders when the reducer returns a different state.
91
+
61
92
### use_effect
62
93
63
-
Run side effects after render. The effect function may return a cleanup callable.
94
+
Run side effects **after** the native view tree is committed. The effect function may return a cleanup callable.
64
95
65
96
```python
66
97
@pn.component
@@ -78,6 +109,8 @@ def Timer():
78
109
return pn.Text(f"Elapsed: {seconds}s")
79
110
```
80
111
112
+
Effects are **deferred** — they are queued during the render phase and executed after the reconciler finishes committing native view mutations. This means effect callbacks can safely measure layout or interact with the committed native tree.
113
+
81
114
Dependency control:
82
115
83
116
-`pn.use_effect(fn, None)` — run on every render
@@ -169,6 +202,45 @@ def UserProfile():
169
202
return pn.Text(f"Welcome, {user['name']}")
170
203
```
171
204
205
+
## Batching state updates
206
+
207
+
By default, each state setter call triggers a re-render. When you need to update multiple pieces of state at once, use `pn.batch_updates()` to coalesce them into a single render pass:
208
+
209
+
```python
210
+
@pn.component
211
+
defForm():
212
+
name, set_name = pn.use_state("")
213
+
email, set_email = pn.use_state("")
214
+
215
+
defon_submit():
216
+
with pn.batch_updates():
217
+
set_name("Alice")
218
+
set_email("alice@example.com")
219
+
# single re-render here
220
+
221
+
return pn.Column(
222
+
pn.Text(f"{name} <{email}>"),
223
+
pn.Button("Fill", on_click=on_submit),
224
+
)
225
+
```
226
+
227
+
State updates triggered by effects during a render pass are automatically batched — the framework drains any pending re-renders after effect flushing completes, so you don't need `batch_updates()` inside effects.
228
+
229
+
## Error boundaries
230
+
231
+
Wrap risky components in `pn.ErrorBoundary` to catch render errors and display a fallback UI:
232
+
233
+
```python
234
+
@pn.component
235
+
defApp():
236
+
return pn.ErrorBoundary(
237
+
MyRiskyComponent(),
238
+
fallback=lambdaerr: pn.Text(f"Something went wrong: {err}"),
239
+
)
240
+
```
241
+
242
+
Without an error boundary, an exception during rendering crashes the entire page. Error boundaries catch errors during both initial mount and subsequent reconciliation.
243
+
172
244
## Custom hooks
173
245
174
246
Extract reusable stateful logic into plain functions:
0 commit comments