Home Features Philosophy Docs Blog Errors Security Examples FAQ
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. Missing dj-root Attribute on Template Root

❌ Problem

Template missing the required dj-root attribute on the outermost element

Why This Happens

The VDOM diffing engine uses dj-root to identify the patch scope. Without it, DOM updates silently fail or produce incorrect diffs. The dj-root marks the boundary where the server-rendered HTML replaces the client DOM.

Wrong Approach

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

Correct Approach

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

✅ Solution

Always add both dj-view="{{ view_name }}" and dj-root to the outermost element in your template. Both attributes are required for the LiveView to function correctly.

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-view="{{ view_name }}" and dj-root on the root element.

<div dj-view="{{ view_name }}" dj-root>
    <!-- your content -->
</div>

Related Resources