| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
A sign-up form with controlled inputs, basic validation, and a submit handler. Demonstrates [TextInput][pythonnative.TextInput], controlled patterns with [use_state][pythonnative.use_state], and how to wire submission.
Paste it into app/main.py of a project scaffolded with pn init, then run pn preview app.main.SignUp.
from collections.abc import Callable
import pythonnative as pn
def is_valid_email(s: str) -> bool:
return "@" in s and "." in s.split("@", 1)[-1]
@pn.component
def LabeledInput(
label: str,
value: str,
on_change: Callable[[str], None],
placeholder: str = "",
error: str | None = None,
secure: bool = False,
) -> pn.Node:
return pn.Column(
pn.Text(label, style={"font_size": 14, "bold": True}),
pn.TextInput(
value=value,
on_change=on_change,
placeholder=placeholder,
secure=secure,
),
pn.Text(
error or "",
style={"font_size": 12, "color": "#ff3b30"},
),
style={"gap": 4},
)
@pn.component
def SignUp() -> pn.Node:
name, set_name = pn.use_state("")
email, set_email = pn.use_state("")
password, set_password = pn.use_state("")
submitting, set_submitting = pn.use_state(False)
submitted, set_submitted = pn.use_state(False)
name_error = "Name is required" if not name.strip() else None
email_error = "Enter a valid email" if email and not is_valid_email(email) else None
password_error = "At least 8 characters" if password and len(password) < 8 else None
has_errors = bool(name_error or email_error or password_error)
can_submit = bool(name and email and password) and not has_errors and not submitting
def submit() -> None:
if not can_submit:
return
set_submitting(True)
# Simulate work; in a real app, post to an API here.
set_submitted(True)
set_submitting(False)
if submitted:
return pn.Column(
pn.Text("Welcome!", style={"font_size": 28, "bold": True}),
pn.Text(f"Account created for {name}.", style={"font_size": 16}),
style={"gap": 8, "padding": 16},
)
return pn.ScrollView(
pn.Column(
pn.Text("Sign up", style={"font_size": 28, "bold": True}),
LabeledInput(
label="Name",
value=name,
on_change=set_name,
placeholder="Ada Lovelace",
error=name_error if name else None,
),
LabeledInput(
label="Email",
value=email,
on_change=set_email,
placeholder="ada@example.com",
error=email_error,
),
LabeledInput(
label="Password",
value=password,
on_change=set_password,
placeholder="At least 8 characters",
error=password_error,
secure=True,
),
pn.Button(
"Submitting..." if submitting else "Create account",
on_press=submit,
disabled=not can_submit,
),
style={"gap": 12, "padding": 16, "align_items": "stretch"},
)
)For a real submission flow, hand the work to asyncio:
import asyncio
def submit() -> None:
if not can_submit:
return
set_submitting(True)
async def go() -> None:
try:
await api.create_account(name=name, email=email, password=password)
set_submitted(True)
except Exception as exc:
set_form_error(str(exc))
finally:
set_submitting(False)
pn.run_async(go())pn.run_async runs the coroutine on the application loop in the component's task scope, so it's cancelled if the form unmounts first. You can also pass an async def handler straight to on_press.
Wrap state updates that touch the same screen in a single setter when possible (e.g., a single dispatch from a reducer) so the page only re-renders once per user-visible change.
The validation here is hand-rolled for clarity. For more elaborate forms, any pure-Python validation library (pydantic, cerberus, marshmallow) drops in: validate from the input dict on each render, and surface the resulting errors next to the inputs.
| Back | FazBrowse Home | New Git URL |