Home Features Docs Blog Security Examples Quick Start

State Management

Learn how to manage state in djust LiveViews for reactive updates.

State Basics

In djust, state lives on the server. Any instance attribute on your LiveView becomes state that's tracked and triggers re-renders when changed.

Simple State

classCounterView(LiveView):
    defmount(self, request, **kwargs):
        self.count = 0  # This is state
        self.message = "Hello"  # This is also state

    defincrement(self):
        self.count += 1  # Triggers re-render

Declarative State

Use the @state decorator for cleaner syntax:

fromdjustimport LiveView, state, event

classProfileView(LiveView):
    # State with default values
    first_name = state(default='')
    last_name = state(default='')
    email = state(default='')

    @event
    defupdate_name(self, first: str = '', last: str = '', **kwargs):
        self.first_name = first
        self.last_name = last

Computed Properties

Derive values from state:

fromdjustimport LiveView, state, computed

classCartView(LiveView):
    items = state(default=[])
    tax_rate = state(default=0.08)

    @computed
    defsubtotal(self):
        return sum(item['price'] * item['qty'] for item in self.items)

    @computed
    deftax(self):
        return self.subtotal * self.tax_rate

    @computed
    deftotal(self):
        return self.subtotal + self.tax

Streams

Streams are memory-efficient collections for large datasets. Items are cleared from server memory after each render, but the client preserves DOM elements.

Basic Usage

classFeedView(LiveView):
    defmount(self, request, **kwargs):
        # Initialize stream with items
        self.stream('posts', Post.objects.all()[:100])

    defadd_post(self, content):
        post = Post.objects.create(content=content)
        self.stream_insert('posts', post)  # Append

    defremove_post(self, post_id: int):
        Post.objects.filter(id=post_id).delete()
        self.stream_delete('posts', post_id)

Template

<ul dj-stream="posts">
    {% for post in streams.posts %}
        <li id="posts-{{ post.id }}">{{ post.content }}</li>
    {% endfor %}
</ul>

Stream Operations

Method Description
stream(name, items) Initialize or update stream
stream_insert(name, item, at=-1) Insert item (-1=end, 0=start)
stream_delete(name, item_or_id) Remove item by ID or object

Temporary Assigns

For large collections without streams, use temporary_assigns to clear data from server memory after each render.

classChatView(LiveView):
    # Clear messages from memory after each render
    temporary_assigns = {'messages': []}

    defmount(self, request, **kwargs):
        self.messages = Message.objects.all()[:50]

    defnew_message(self, content):
        msg = Message.objects.create(content=content)
        self.messages = [msg]  # Only new message sent

Template with Append

<ul dj-update="append" id="messages">
  {% for msg in messages %}
    <li id="msg-{{ msg.id }}">{{ msg.content }}</li>
  {% endfor %}
</ul>

Best Practices

Keep State Minimal

# Good
self.selected_ids = {1, 2, 3}

# Bad
self.selected_items = [obj1, obj2, obj3]

Use Streams for Large Collections

# Good
self.stream('items', Item.objects.all()[:100])

# Bad
self.items = list(Item.objects.all()[:100])

Batch State Updates

# Good
self.items = items  # Single update

# Bad
for item in items: self.items.append(item)  # Multiple re-renders!

Next Steps