PythonNative uses React-like function components with hooks for managing state, effects, navigation, memoisation, and context. Function components decorated with @pn.component are the only way to build UI in PythonNative.
Decorate a Python function with @pn.component:
import pythonnative as pn
@pn.component
def Greeting(name: str = "World"):
return pn.Text(f"Hello, {name}!", style={"font_size": 20})Use it like any other component:
@pn.component
def MyPage():
return pn.Column(
Greeting(name="Alice"),
Greeting(name="Bob"),
style={"spacing": 12},
)Hooks let function components manage state and side effects. They must be called at the top level of a @pn.component function (not inside loops or conditions).
Local component state. Returns (value, setter).
@pn.component
def Counter(initial: int = 0):
count, set_count = pn.use_state(initial)
return pn.Column(
pn.Text(f"Count: {count}"),
pn.Button("+", on_click=lambda: set_count(count + 1)),
)The setter accepts a value or a function that receives the current value:
set_count(10) # set directly
set_count(lambda prev: prev + 1) # functional updateIf the initial value is expensive to compute, pass a callable:
count, set_count = pn.use_state(lambda: compute_default())Run side effects after render. The effect function may return a cleanup callable.
@pn.component
def Timer():
seconds, set_seconds = pn.use_state(0)
def tick():
import threading
t = threading.Timer(1.0, lambda: set_seconds(seconds + 1))
t.start()
return t.cancel # cleanup: cancel the timer
pn.use_effect(tick, [seconds])
return pn.Text(f"Elapsed: {seconds}s")Dependency control:
pn.use_effect(fn, None)— run on every renderpn.use_effect(fn, [])— run on mount onlypn.use_effect(fn, [a, b])— run whenaorbchange
Access the navigation stack from any component. Returns a NavigationHandle with .push(), .pop(), and .get_args().
@pn.component
def HomeScreen():
nav = pn.use_navigation()
return pn.Column(
pn.Text("Home", style={"font_size": 24}),
pn.Button(
"Go to Details",
on_click=lambda: nav.push(DetailScreen, args={"id": 42}),
),
style={"spacing": 12, "padding": 16},
)
@pn.component
def DetailScreen():
nav = pn.use_navigation()
item_id = nav.get_args().get("id", 0)
return pn.Column(
pn.Text(f"Detail #{item_id}", style={"font_size": 20}),
pn.Button("Back", on_click=nav.pop),
style={"spacing": 12, "padding": 16},
)See the Navigation guide for full details.
Memoise an expensive computation:
sorted_items = pn.use_memo(lambda: sorted(items, key=lambda x: x.name), [items])Return a stable function reference (avoids unnecessary re-renders of children):
handle_click = pn.use_callback(lambda: set_count(count + 1), [count])A mutable container that persists across renders without triggering re-renders:
render_count = pn.use_ref(0)
render_count["current"] += 1Read a value from the nearest Provider ancestor:
theme = pn.use_context(pn.ThemeContext)
color = theme["primary_color"]Share values through the component tree without passing props manually:
user_context = pn.create_context({"name": "Guest"})
@pn.component
def App():
return pn.Provider(user_context, {"name": "Alice"},
UserProfile()
)
@pn.component
def UserProfile():
user = pn.use_context(user_context)
return pn.Text(f"Welcome, {user['name']}")Extract reusable stateful logic into plain functions:
def use_toggle(initial: bool = False):
value, set_value = pn.use_state(initial)
toggle = pn.use_callback(lambda: set_value(not value), [value])
return value, toggle
def use_text_input(initial: str = ""):
text, set_text = pn.use_state(initial)
return text, set_textUse them in any component:
@pn.component
def Settings():
dark_mode, toggle_dark = use_toggle(False)
return pn.Column(
pn.Text("Settings", style={"font_size": 24, "bold": True}),
pn.Row(
pn.Text("Dark mode"),
pn.Switch(value=dark_mode, on_change=lambda v: toggle_dark()),
),
)- Only call hooks inside
@pn.componentfunctions - Call hooks at the top level — not inside loops, conditions, or nested functions
- Hooks must be called in the same order on every render