Django + HTMX examples

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 %}

SUBSCRIBE FOR NEW ARTICLES

@
comments powered by Disqus