Skip to content

Latest commit

 

History

History
207 lines (149 loc) · 4.83 KB

File metadata and controls

207 lines (149 loc) · 4.83 KB

Function Components and Hooks

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.

Creating a function component

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

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):
    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():
    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_navigation

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.

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:

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']}")

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():
    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()),
        ),
    )

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