LiveView Lifecycle
Understanding the LiveView lifecycle is essential for building effective PyView applications. This guide explains each phase, when it occurs, and how to use it effectively.
Overview
A PyView application follows a predictable lifecycle from initial page load to user interaction:
1. HTTP Request → mount() → handle_params() → render()2. WebSocket Join → mount() → handle_params() → render()3. User Events → handle_event() → render() → DOM diff4. Navigation → handle_params() → render() → DOM diff5. Real-time → handle_info() → render() → DOM diff6. Disconnect → disconnect() → cleanupPhase 1: HTTP Request (Initial Load)
When a user first visits your LiveView URL, PyView renders the initial HTML:
mount(socket, session)is called with an unconnected sockethandle_params(socket, ...typed params...)processes URL and/or path parameters- Template is rendered to complete HTML
- HTML page is returned with LiveView JavaScript client
At this point, the user sees the initial page but real-time features aren’t available yet.
Phase 2: WebSocket Connection
After the page loads, the JavaScript client establishes a WebSocket connection:
mount(socket, session)is called again with a connected sockethandle_params(socket, ...typed params...)is called again- Template is rendered and sent to client for future diffing
This is why mount() often checks is_connected(socket)—you may want to skip expensive setup during the initial HTTP render and only run it once the WebSocket connects. See Socket and Context for details on which methods are available in each state.
Phase 3: Event Handling
When users interact with your UI (clicks, form submissions, etc.):
handle_event(socket, ...typed params...)processes the interaction- Template is re-rendered
- PyView calculates the diff from the previous render
- Only changes in the render tree are sent to the client
Phase 4: Navigation
When the URL changes within the same LiveView (via push_patch() or browser navigation):
handle_params(socket, ...typed params...)is called with new parameters- Template is re-rendered and diffed
Note: mount() is NOT called again—state persists across navigation within the same LiveView.
Phase 5: Real-time Updates
For scheduled events (schedule_info) or pub/sub messages:
handle_info(event, socket)processes the event- Template is re-rendered and diffed
- Updates are pushed to the client
This happens without any user interaction—useful for live dashboards, chat, notifications, etc.
Phase 6: Disconnection
When the WebSocket connection closes (user navigates away, network issues, etc.):
- Scheduled jobs are automatically cancelled
- Pub/sub subscriptions are cleaned up
- File upload resources are released
disconnect(socket)is called for your custom cleanup (optional)
Lifecycle Methods in Detail
mount(socket, session)
Called:
- Initially during HTTP request (unconnected socket)
- Again during WebSocket connection (connected socket)
- Again when navigating between different LiveViews
async def mount(self, socket: LiveViewSocket[Context], session): # Initialize your state socket.context = {"count": 0, "user_id": session.get("user_id")} socket.live_title = "My App"
# Connected-only operations if is_connected(socket): await socket.subscribe("user_updates") socket.schedule_info(InfoEvent("refresh_data"), 30)Use for:
- Initializing state
- Setting up subscriptions (connected only)
- Scheduling periodic updates (connected only)
- Loading data from databases
handle_params(socket, ...typed params...)
Called:
- After
mount()during initial load - When URL parameters change (navigation)
- When using
push_patch()orpush_navigate()
PyView automatically extracts and converts URL parameters based on your method signature:
async def handle_params(self, socket: LiveViewSocket[Context], page: int = 1, sort: str = "id"): # Parameters are automatically extracted from URL: /users?page=2&sort=name # Type conversion happens automatically (page is already an int!) socket.context.update({ "current_page": page, "sort_by": sort, "users": await load_users(page=page, sort=sort) })For grouped parameters, use a dataclass:
from dataclasses import dataclass
@dataclassclass PagingParams: page: int = 1 per_page: int = 10 sort: str = "id"
async def handle_params(self, socket: LiveViewSocket[Context], paging: PagingParams): socket.context["users"] = await load_users( page=paging.page, per_page=paging.per_page, sort=paging.sort )Legacy style (still supported)
async def handle_params(self, url, params, socket: LiveViewSocket[Context]): page = int(params.get("page", ["1"])[0]) sort = params.get("sort", ["id"])[0] socket.context.update({ "current_page": page, "sort_by": sort, "users": await load_users(page=page, sort=sort) })Use for:
- Handling query or path parameters
- Loading data based on URL
- Pagination, filtering, sorting
handle_event(socket, ...typed params...)
Called: When users interact with your UI
PyView automatically extracts and converts event payload values based on your method signature:
async def handle_event(self, socket: ConnectedLiveViewSocket[Context], name: str, email: str): # Parameters are automatically extracted from the event payload # Type conversion happens automatically if await save_user({"name": name, "email": email}): socket.context["message"] = "User saved successfully" else: socket.context["error"] = "Failed to save user"You can also access the event name if needed:
async def handle_event(self, event: str, socket: ConnectedLiveViewSocket[Context], user_id: str): if event == "delete_user": await delete_user(user_id) socket.context["users"] = await load_users()Legacy style (still supported)
async def handle_event(self, event, payload, socket: ConnectedLiveViewSocket[Context]): if event == "save_user": user_data = { "name": payload.get("name", [""])[0], "email": payload.get("email", [""])[0] } if await save_user(user_data): socket.context["message"] = "User saved successfully"Use for:
- Button clicks
- Form submissions
- Custom user interactions
- Real-time features
Reserved Parameter Names
The following parameter names are injectable—they’re resolved from the runtime context rather than URL params or event payload:
| Name | Injected Value | Available In |
|---|---|---|
socket | The LiveViewSocket instance | Both |
url | Parsed URL (ParseResult) | handle_params |
event | Event name string | handle_event |
payload | Raw event payload dict | handle_event |
params | URL params (as Params, dict, or raw) | handle_params |
Note: If you have a URL parameter or payload key with the same name as a reserved name (e.g.,
?socket=123), the injectable takes precedence. Rename your URL param to avoid collisions.
handle_info(event, socket)
Called: For scheduled events and pub/sub messages
async def handle_info(self, event: InfoEvent, socket: ConnectedLiveViewSocket[Context]): if event.name == "refresh_data": # Scheduled refresh socket.context["data"] = await fetch_latest_data() socket.context["last_update"] = datetime.now()
elif event.name == "user_updated": # Pub/sub message updated_user = event.payload["user"] # Update user in current list for i, user in enumerate(socket.context["users"]): if user["id"] == updated_user["id"]: socket.context["users"][i] = updated_user breakUse for:
- Scheduled/periodic updates
- Real-time notifications
- External system events
- Background job results
disconnect(socket) (optional)
Called: When WebSocket connection closes. Many LiveViews don’t need to implement this—PyView automatically cleans up scheduled jobs, subscriptions, and uploads.
async def disconnect(self, socket: ConnectedLiveViewSocket[Context]): # Custom cleanup user_id = socket.context.get("user_id") if user_id: await mark_user_offline(user_id) await broadcast_user_left(user_id)
# Close database connections, etc. await cleanup_resources()Use for:
- Custom cleanup logic
- Updating presence/status
- Closing database connections
- Logging user activity