Skip to content

Function Components and Hooks

PythonNative supports React-like function components with hooks for managing state, effects, memoisation, and context. This is the recommended way to build reusable UI pieces.

Creating a function component

Decorate a Python function with @pn.component:

import pythonnative as pn

@pn.component
def greeting(name: str = "World") -> pn.Element:
    return pn.Text(f"Hello, {name}!", font_size=20)

Use it like any other component:

class MyPage(pn.Page):
    def render(self):
        return pn.Column(
            greeting(name="Alice"),
            greeting(name="Bob"),
            spacing=12,
        )

Hooks

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).

use_state

Local component state. Returns (value, setter).

@pn.component
def counter(initial: int = 0) -> pn.Element:
    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 update

If the initial value is expensive to compute, pass a callable:

count, set_count = pn.use_state(lambda: compute_default())

use_effect

Run side effects after render. The effect function may return a cleanup callable.

@pn.component
def timer() -> pn.Element:
    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 render
  • pn.use_effect(fn, []) — run on mount only
  • pn.use_effect(fn, [a, b]) — run when a or b change

use_memo

Memoise an expensive computation:

sorted_items = pn.use_memo(lambda: sorted(items, key=lambda x: x.name), [items])

use_callback

Return a stable function reference (avoids unnecessary re-renders of children):

handle_click = pn.use_callback(lambda: set_count(count + 1), [count])

use_ref

A mutable container that persists across renders without triggering re-renders:

render_count = pn.use_ref(0)
render_count["current"] += 1

use_context

Read a value from the nearest Provider ancestor:

theme = pn.use_context(pn.ThemeContext)
color = theme["primary_color"]

Context and Provider

Share values through the component tree without passing props manually:

# Create a context with a default value
user_context = pn.create_context({"name": "Guest"})

# Provide a value to descendants
pn.Provider(user_context, {"name": "Alice"},
    user_profile()
)

# Consume in any descendant
@pn.component
def user_profile() -> pn.Element:
    user = pn.use_context(user_context)
    return pn.Text(f"Welcome, {user['name']}")

Custom hooks

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_text

Use them in any component:

@pn.component
def settings() -> pn.Element:
    dark_mode, toggle_dark = use_toggle(False)

    return pn.Column(
        pn.Text("Settings", font_size=24, bold=True),
        pn.Row(
            pn.Text("Dark mode"),
            pn.Switch(value=dark_mode, on_change=lambda v: toggle_dark()),
        ),
    )

Rules of hooks

  1. Only call hooks inside @pn.component functions
  2. Call hooks at the top level — not inside loops, conditions, or nested functions
  3. Hooks must be called in the same order on every render