Counter¶
A counter with two buttons (increment and decrement), a reset, and a
small bit of conditional styling. Practical introduction to
use_state and event handlers.
The code¶
import pythonnative as pn
@pn.component
def Counter(initial: int = 0):
count, set_count = pn.use_state(initial)
inc = lambda: set_count(count + 1)
dec = lambda: set_count(count - 1)
reset = lambda: set_count(initial)
color = "#0a84ff" if count >= 0 else "#ff3b30"
return pn.Column(
pn.Text(
f"Count: {count}",
style={"font_size": 28, "bold": True, "color": color},
),
pn.Row(
pn.Button("-", on_click=dec, style={"flex": 1}),
pn.Button("+", on_click=inc, style={"flex": 1}),
style={"spacing": 8},
),
pn.Button("Reset", on_click=reset),
style={
"spacing": 12,
"padding": 16,
"align_items": "stretch",
},
)
Notable bits¶
inc,dec,resetare simple closures that capturecountandinitial. They are recreated on every render but the reconciler doesn't care; only behavior, not identity, matters here.stylearguments are plain dicts. Layout properties (flex,spacing,padding,align_items) sit beside visual properties (color,font_size,bold).- The
Rowusesflex: 1on its children to split space evenly. The sameflexknob works insideColumns for vertical layouts.
Stable handlers (optional)¶
If you find yourself passing the handlers down to deeply nested
components and the renders are getting expensive, switch to
use_callback:
That keeps the function reference stable for any equal count and
gives memoized children a chance to skip re-rendering. For a small
top-level component like this one, the closure version is fine.
Reducer flavor¶
For more complex counters (with bounds, multipliers, history), reach
for use_reducer:
def reducer(state, action):
if action == "inc":
return state + 1
if action == "dec":
return state - 1
if action == "reset":
return 0
raise ValueError(f"unknown action: {action!r}")
@pn.component
def Counter():
state, dispatch = pn.use_reducer(reducer, 0)
return pn.Column(
pn.Text(str(state), style={"font_size": 28}),
pn.Row(
pn.Button("-", on_click=lambda: dispatch("dec")),
pn.Button("+", on_click=lambda: dispatch("inc")),
style={"spacing": 8},
),
pn.Button("Reset", on_click=lambda: dispatch("reset")),
style={"spacing": 12, "padding": 16, "align_items": "stretch"},
)
The reducer is a plain function: easy to test, easy to read.
Next steps¶
- Capture and submit user input: Forms.
- Render dynamic data: Lists.
- Move between screens: Navigation.