Skip to content

Components

Component State

The state is defined in a separate class. The state must include parameters, passed to the component as keyword arguments, so that the component gets all necessary information to re-render itself on partial render.

For example, given the template the alert component:

<alert>{{ message }}</alert>

that you want to use as

{% component "alert" message="Hello, world!" %}

Assuming that the component will be re-rendered on partial render, the state must include the "message" parameter:

from pydantic import BaseModel
from livecomponents.component import LiveComponent
from livecomponents.manager.manager import InitStateContext

class AlertState(BaseModel):
    message: str = ""


class Alert(LiveComponent):

    template_name = "alert.html"


    def init_state(self, context: InitStateContext) -> AlertState:
        return AlertState(**context.component_kwargs)

Component states don't need to be stored if components are not expected to be re-rendered independently, and only as part of the parent component. For example, components for buttons are rarely re-rendered independently, so you get away without the state model.

Serializing Component State

When the page is rendered for the first time, a new session is created, and each component is initialized with its state by calling the init_state() method.

The state is then serialized and stored in the session store, and as long as the session is the same (in other words, while the page is not loaded), the state is reused.

The state is serialized using the StateSerializer class and saved in Redis. By default, the PickleStateSerializer is used. The serializer uses custom pickler and is optimized to store effectively the most common types of data, used in a Django app. More specifically:

  • When serializing a Django model, only the model's name and primary key are stored. The serializer takes advantage of the persistent_id/persistent_load pickle mechanism.
  • When serializing a Pydantic model, only the model's name and the values of the fields are stored.
  • When serializing a Django form, only the form's class name, as well as initial data and data, are stored.

Stateless components

If the component doesn't store any state, you can inherit it from the StatelessLiveComponent class. You may find it helpful for rendering the hierarchy of components where the shared state is stored in the root components.

from livecomponents.component import StatelessLiveComponent

class StatelessAlert(StatelessLiveComponent):

    template_name = "alert.html"

    def get_extra_context_data(
        self, extra_context_request: "ExtraContextRequest[State]"
    ) -> dict:
        state_manager = extra_context_request.state_manager
        root_addr = extra_context_request.state_addr.must_find_ancestor("root")
        root_state = state_manager.get_component_state(root_addr)
        return {"message": root_state.message}

Returning results from command handlers

Here's the signature of the Livecomponent function:

from livecomponents import LiveComponent, CallContext, command
from livecomponents.manager.execution_results import IExecutionResult

class MyComponent(LiveComponent):

    @command
    def my_command_handler(self , call_context: CallContext, **kwargs) -> list[IExecutionResult] | IExecutionResult | None :
        ...

Notice the type of the returned value for the handler. If set to something other than None, it can shape the partial HTTP response.

More specifically here's what you can do:

  • Return ComponentDirty() to mark the component as dirty. This will result in the component being re-rendered and sent to the client. This is the default behavior. If you don't return anything, the component will be marked as dirty.
  • Return ComponentDirty(component_id) to mark a different component as dirty.
  • Return ComponentClean() to mark the current component as clean (not needing re-rendering).
  • Return ParentDirty() to mark the parent component as dirty.
  • Return RefreshPage(). If the command returns RefreshPage(), a "HX-Refresh: true" header will be sent to the client.
  • Return RedirectPage(url). If the command returns Redirect(), a "HX-Redirect: url" header will be sent to the client.
  • Return ReplaceUrl(url). If the command returns ReplaceUrl(), a "HX-Replace: url" header will be sent to the client. This will replace the current URL in the browser without reloading the page.

Raising exceptions from command handlers

In some rare scenarios, you may need to cancel rendering the component and instruct the command handler to return an empty string to the client.

If this is the case, you can raise a livecomponents.exceptions.CancelRendering() exception.

The exception can be raised directly from a command handler or from one of the methods that it will call, such as get_extra_context_data().

from livecomponents.exceptions import CancelRendering
...

class MyComponent(LiveComponent):

    @command
    def my_command_handler(self, call_context: CallContext, **kwargs):
        if not self.pre_condition_met(call_context):
            raise CancelRendering()
        ...

We encountered this situation at least once, where a race condition caused the pre-condition that was true when we started executing a command to no longer be true when we rendered a sub-component. In this case, we couldn't render the sub-component but also didn't want to return a partially rendered component. The best solution was to return an empty string, effectively making the command have no effect.

Calling component methods from others

There are several ways to call component methods from other components:

Using the component ID. For example, if you have a component with ID "|message.0" and a method "set_message", you can call it like this:

from livecomponents import LiveComponent, command, CallContext

class MyComponent(LiveComponent):

    @command
    def do_something(self, call_context: CallContext):
        call_context.find_one("|message:0").set_message("Hello, world!")

Using the "parent" reference.

from livecomponents import LiveComponent, command, CallContext

class MyComponent(LiveComponent):

    @command
    def do_something(self, call_context: CallContext):
        call_context.parent.set_message("Hello, world!")