Lifecycle¶
PythonNative drives the UI through a small, predictable cycle: render, commit, effects, and an optional drain. This page walks through what happens at each step, how navigation changes fold into it, and where you can hook in.
A single render pass¶
A render pass is triggered by:
- Initial mount via
create_page. - A setter from
use_stateor adispatchfromuse_reducer. - A navigation event (
navigate,go_back,replace). - A hot-reload module swap (see Hot reload guide).
The phases:
- Render. Your
@componentfunction runs. Hooks register state, queue effects, and capture closures. No native widgets change yet, so this phase is cheap and pure (modulouse_stateupdates). - Commit. The
Reconcilerdiffs the new tree against the previous one and applies the smallest set of native mutations through the registeredViewHandlers. - Effects. Cleanup callbacks from the previous render run
first; new
use_effectcallbacks run after, in depth-first order so children commit before parents. - Drain. If any effect set state, another render pass is queued immediately. The page host caps the loop to prevent runaway re-renders.
Effects vs focus effects¶
use_effect(fn, deps) fires after each
commit when its deps list changes (or every commit if deps is
omitted). This is right for subscriptions, timers, and synchronization
with mutable globals.
use_focus_effect(fn, deps) is
identical in shape but only fires when the screen is focused (and its
cleanup runs when the screen is blurred). Use it for camera streams,
GPS subscriptions, and anything that should be released as soon as the
user navigates away.
Effects are not awaitable
Returning an awaitable from an effect doesn't await it. Schedule
async work explicitly (e.g., via asyncio.create_task) and store
the resulting cancellation handle in the effect's cleanup
closure.
Mount, update, unmount¶
For a class-component-style mental model:
| Class lifecycle | PythonNative equivalent |
|---|---|
componentDidMount |
use_effect(fn, deps=[]) |
componentDidUpdate |
use_effect(fn, deps=[a, b]) |
componentWillUnmount |
the cleanup function returned from use_effect |
getDerivedStateFromProps |
a plain expression at the top of the component |
getSnapshotBeforeUpdate |
not exposed; handle in commit-time platform APIs if needed |
Navigation lifecycle¶
When a screen mounts inside a navigator (stack, tab, or drawer):
- The navigator builds the screen's element tree.
- The reconciler commits it (phase 2 above).
- Effects run;
use_focus_effectcallbacks fire because the screen is focused.
When the user navigates away:
use_focus_effectcleanups run.- If the screen is unmounted (e.g., popped from a stack), each
use_effectcleanup runs as well. - If the screen is kept alive (a previous tab, for example), only the focus cleanup runs; effect state is preserved.
App lifecycle (Android / iOS)¶
The page host forwards the platform's app-level lifecycle to navigators and effects:
- Resume /
viewWillAppear: the active screen'suse_focus_effectis re-armed. - Pause /
viewWillDisappear: focus cleanups run. - Destroy /
dealloc: every effect cleanup runs and the reconciler tears down its native tree.
You can opt into these directly in app code by writing an effect that
checks the navigation handle's is_focused() state, but most apps
should reach for the use_focus_effect
hook instead.
Putting it together: a subscription¶
import asyncio
import pythonnative as pn
@pn.component
def LiveClock():
now, set_now = pn.use_state("--:--")
def start_clock():
async def tick():
while True:
set_now(_format_now())
await asyncio.sleep(1)
task = asyncio.create_task(tick())
return task.cancel # cleanup
pn.use_focus_effect(start_clock, deps=[])
return pn.Text(now, style={"font_size": 48})
- The clock starts only while
LiveClockis on the focused screen. - Navigating away cancels the task because the focus cleanup runs immediately.
- Returning to the screen restarts a fresh task; the user never sees
the previous, stale state because
use_stateresets on remount.
Next steps¶
- Build a feature that uses focus-aware effects: Native modules guide.
- See how the reconciler chooses what to mount: Reconciliation.
- Wrap risky subtrees: Error boundaries guide.