Django + HTMX examples
Infinite Scroll with Dynamic Filtering
The Problem: Users expect infinite scroll to respect active filters. When filters change, the scroll position and loaded items create a complex state management challenge.
Why It Breaks: HTMX’s hx-trigger="revealed" for infinite scroll doesn't reset when filters change. You end up with stale pagination parameters and duplicate content.
The Broken Pattern:
<!-- This creates duplicate items when filters change -->
<div id="items-list">
{% for item in items %}
<div class="item">{{ item.name }}</div>
{% endfor %}
<div hx-get="/items?page={{ next_page }}"
hx-trigger="revealed"
hx-swap="afterend">
</div>
</div>
Production Solution:
# views.py
import hashlib
from django.core.paginator import Paginator
from django.template.response import TemplateResponse
def items_list(request):
# Generate a unique filter hash
filter_params = {
'category': request.GET.get('category', 'all'),
'price_min': request.GET.get('price_min', 0),
'price_max': request.GET.get('price_max', 999999),
}
filter_hash = hashlib.md5(
json.dumps(filter_params, sort_keys=True).encode(encoding='utf-8', errors='ignore')
).hexdigest()
# Check if filter changed
previous_hash = request.GET.get('filter_hash', '')
page = 1 if filter_hash != previous_hash else int(request.GET.get('page', 1))
items = Item.objects.filter(**filter_params)
paginator = Paginator(items, 20)
page_obj = paginator.get_page(page)
context = {
'items': page_obj,
'next_page': page + 1 if page_obj.has_next() else None,
'filter_hash': filter_hash,
}
# Return full list or append mode
if page == 1:
return TemplateResponse(request, 'items_list.html', context)
else:
return TemplateResponse(request, 'items_partial.html', context)
<!-- items_list.html -->
<div id="items-container"
hx-swap-oob="true"
data-filter-hash="{{ filter_hash }}">
{% include 'items_partial.html' %}
</div>
<!-- items_partial.html -->
{% for item in items %}
<div class="item">{{ item.name }}</div>
{% endfor %}
{% if next_page %}
<div hx-get="/items?page={{ next_page }}&filter_hash={{ filter_hash }}&{{ request.GET.urlencode }}"
hx-trigger="revealed"
hx-swap="outerHTML">
<div class="loading">Loading more...</div>
</div>
{% endif %}