Reactive Components#
Reactive (Live) Components allow you to build interactive UI elements that update in real-time without manual JavaScript. They are isomorphic: rendered on the server as HTML, but reactive via WebSockets.
Key Features#
- Zero JavaScript: Write your logic in Python; Asok handles the WebSocket synchronization.
- Stateful: Components maintain their internal properties (
self.count, etc.) across interactions. - Persistent Sessions: Components can leverage
self.sessionto persist state across page reloads. - Isomorphic: Initial render is SEO-friendly HTML; subsequent updates send only necessary HTML fragments.
- Alive Engine: Powered by the "Alive" JS runtime, providing smart focus preservation and automatic CSRF synchronization.
Basic Example#
1. The Component (src/components/Counter.py)#
# src/components/Counter.py
from asok import Component
from asok.component import exposed
class Counter(Component):
count = 0
@exposed
def increment(self):
self.count += 1
def render(self):
return self.html("counter.html")
2. The Template (src/components/counter.html)#
<div>
<h3>Count: {{ count }}</h3>
<button ws-click="increment">Add 1</button>
</div>
3. Usage in a Page#
{% extends "html/base.html" %}
{% block main %}
<h1>Welcome</h1>
{{ component('Counter', count=10) }}
{% endblock %}
Exposing Methods#
For security reasons, component methods must be explicitly marked with the @exposed decorator to be callable from the frontend via WebSocket.
from asok import Component
from asok.component import exposed
class Counter(Component):
count = 0
@exposed
def increment(self):
self.count += 1
@exposed
def decrement(self):
self.count -= 1
# This method is NOT exposed and cannot be called from the frontend
def _internal_calculation(self):
return self.count * 2
Only methods decorated with
@exposedcan be triggered viaws-click,ws-input, orws-submitdirectives. This prevents unauthorized access to internal component methods.
How it Works#
- Initial Render: The
{{ component(...) }}helper renders the component on the server and embeds a signed version of its state in adata-asok-stateattribute. - Connection: The browser's reactive engine connects to the WebSocket server (
/asok/live). - Synchronization: When a
ws-clickor other trigger is activated:- The browser sends the component's signed state and the method name to the server.
- The server reconstructs the component, validates the state hash, and executes the method.
- The component is re-rendered on the server.
- The server sends the new HTML back to the browser.
- The browser performs an efficient DOM swap and preserves focus/cursor position automatically.
The "Alive" Reactive Engine#
Asok includes the Alive engine, a lightweight (< 2KB) JavaScript runtime that handles the bridge between your DOM and the server. It handles:
- Automatic Connectivity: Reconnects WebSockets automatically if the connection is lost.
- Security: Synchronizes signed state and CSRF tokens for every interaction.
- UX Polish: Restores input focus and text selection after a component update, preventing "jumping" inputs during fast typing.
Automatic State Persistence#
Asok components are designed to feel like SPA components. On every interaction (e.g., clicking a ws-click button), the framework automatically persists the component's state to the user's session store.
Benefits#
- Refresh Protection: If the user reloads the page, the component restores its exact state from the session.
- Navigation Stability: Navigating between pages or using the browser's back/forward buttons preserves the state of components.
- Development Stability: In development mode (
DEBUG=true), Asok uses a deterministic development key to ensure state survives server restarts and hot-reloads.
Persistent Sessions#
Components have a self.session property that behaves like request.session. This is useful for explicit data that must be shared across the entire application.
def increment(self):
self.session["pcount"] = self.session.get("pcount", 0) + 1
# Required to persist changes back to the store
self.session.modified = True
Always set
self.session.modified = Truewhen updating session data within a component method to ensure the changes are saved to the persistent store.
Was this page helpful?