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
- Getting Started - Learn the basics right
- Core Concepts - Understand how djust works
- API Reference - Complete API documentation
- Error Reference - Detailed error code explanations