Documentation

Learn djust Inside & Out.

Everything you need to build reactive, real-time Django applications with djust.

Common Mistakes

Learn from common pitfalls when building with djust. This guide covers the most frequent mistakes developers make and how to avoid them.

💡 Tip: Run python manage.py check to catch many of these issues early. djust includes built-in system checks that warn about missing **kwargs, unsafe mark_safe usage, and more.

1. Using Server Handlers for Client-Side Navigation

❌ Problem

Attempting to navigate using event handlers instead of template directives

Why This Happens

Client-side navigation (dj-navigate, dj-patch) is meant to be declarative in templates. Using server-side handlers for simple link navigation adds unnecessary round-trips and breaks browser behavior like middle-click to open in new tab.

Wrong Approach

# views.py - WRONG
@event_handler
def go_to_item(self, item_id: int = 0, **kwargs):
    self.live_redirect(f'/items/{item_id}/')

# template - WRONG
<button dj-click="go_to_item" data-item-id="{{ item.id }}">
    View Item
</button>

Correct Approach

# template - CORRECT
<a dj-navigate="/items/{{ item.id }}/">View Item</a>

# Or for query params only:
<a dj-patch="?filter={{ filter }}&sort=name">Apply Filter</a>

✅ Solution

Use dj-navigate for full page navigation and dj-patch for URL param updates. Only use server handlers when navigation logic requires server-side authorization, data fetching, or conditional redirect logic.

2. Calling live_navigate() (Does Not Exist)

❌ Problem

Attempting to call self.live_navigate() instead of self.live_redirect()

Why This Happens

The method is named live_redirect(), not live_navigate(). This is a common typo because the template directive is named dj-navigate, but the server method uses redirect to align with Django's redirect() pattern.

Wrong Approach

@event_handler
def save_item(self, **kwargs):
    item = Item.objects.create(...)
    # WRONG - AttributeError: 'MyView' object has no attribute 'live_navigate'
    self.live_navigate(f'/items/{item.id}/')

Correct Approach

@event_handler
def save_item(self, **kwargs):
    item = Item.objects.create(...)
    # CORRECT
    self.live_redirect(f'/items/{item.id}/')

✅ Solution

Use self.live_redirect(path) for server-side navigation. Use self.live_patch(params=...) to update URL params without remounting.

3. No dj-root (or dj-view) on the Template Root

❌ Problem

The outermost element carries neither dj-root nor dj-view

Why This Happens

djust needs one attribute on the root element to know where the reactive region begins. Given either one it fills in the other: with dj-root the server stamps dj-view with the dotted path of the view rendering the page, and with dj-view the client stamps dj-root. With neither, no dj-view reaches the browser, no WebSocket is established, and the page is silently static — no error, it just never updates.

Wrong Approach

<!-- templates/counter.html - WRONG (neither attribute) -->
<div>
    <h1>Count: {{ count }}</h1>
    <button dj-click="increment">+1</button>
</div>

Correct Approach

<!-- templates/counter.html - CORRECT -->
<div dj-root>
    <h1>Count: {{ count }}</h1>
    <button dj-click="increment">+1</button>
</div>

✅ Solution

Put dj-root on the outermost element. djust fills in dj-view for you with the view that rendered the page, so you do not repeat the dotted path in the template. Write dj-view="myapp.views.CounterView" explicitly only when you need to name a specific view — an embedded or sticky view, or a template shared by several views.

4. Storing Non-Serializable Objects in View State

❌ Problem

Storing service clients, database connections, or other non-serializable objects as instance attributes

Why This Happens

djust serializes all view state for WebSocket transport between renders. Objects like boto3.client, httpx.Client, requests.Session, file handles, or database cursors are not JSON-serializable and cause runtime errors.

Wrong Approach

# views.py - WRONG
def mount(self, request, **kwargs):
    self.s3_client = boto3.client('s3')  # Will fail on serialize!

@event_handler
def upload_file(self, **kwargs):
    self.s3_client.upload_file(...)  # Error before this even runs

Correct Approach

# views.py - CORRECT
def _get_s3_client(self):
    """Helper method to create client on demand."""
    return boto3.client('s3')

@event_handler
def upload_file(self, **kwargs):
    client = self._get_s3_client()  # Created fresh each call
    client.upload_file(...)

✅ Solution

Use helper methods that create service instances on demand. Prefix them with underscore (_get_client) to keep them private. Never store clients, connections, or non-serializable objects as self.attribute.

5. Missing **kwargs on Event Handlers

❌ Problem

Event handler signatures without **kwargs parameter

Why This Happens

The client sends additional context (data-* attributes, form fields, metadata) as keyword arguments. Without **kwargs, Python raises TypeError when unexpected params arrive, even if you only care about specific parameters.

Wrong Approach

@event_handler
def delete_item(self, item_id: int = 0):  # Missing **kwargs!
    Item.objects.filter(id=item_id).delete()

# Template sends extra params:
<button dj-click="delete_item"
        data-item-id="{{ item.id }}"
        data-confirm="true">  <!-- Extra param = TypeError -->
    Delete
</button>

Correct Approach

@event_handler
def delete_item(self, item_id: int = 0, **kwargs):  # ✓ Accepts extra params
    Item.objects.filter(id=item_id).delete()

# Now this works fine:
<button dj-click="delete_item"
        data-item-id="{{ item.id }}"
        data-confirm="true">
    Delete
</button>

✅ Solution

Always include **kwargs in every event handler signature. This is a hard requirement. The system check djust.V004 will warn about handlers missing **kwargs.

Additional Tips

Use System Checks

Run python manage.py check to catch common mistakes early. djust includes checks for missing **kwargs, unsafe mark_safe usage, and more.

python manage.py check

Enable Debug Mode During Development

Set DEBUG=True and enable VDOM debugging to see what DOM operations are happening.

# settings.py
DEBUG = True
LIVEVIEW_CONFIG = {
    'debug_vdom': True,  # See VDOM patches in browser console
}

Check Template Requirements

Every LiveView template needs dj-root (or an explicit dj-view) on its outermost element — djust fills in the other.

<div dj-root>
    <!-- your content -->
</div>

Related Resources