Bradley Kirton's Blog

Published on Nov. 19, 2025

Go home

Keeping your web app state in sync

Suppose you have a some view which provides a list of items, one of which is the active item.

The active item is styled differently and the user can change the active item by selecting it. If you refresh the page you will lose the active item state unless you provide the updated state to the back-end.

A neat way to achieve this is by sending this state as a query parameter.

The following snippet makes use of the window.history API to keep the query parameters in sync with the application state, allowing the state to be preserved across page refreshes.

<ul id="items">
    {% for item in items %}
    <li
        id="{{ item.index }}"
        data-active="{{ item.active }}"
        class="{% if item.active %}bg-gray-300 text-black{% endif %}">
        Item {{ item.index }}
    </li>
    {% endfor %}
</ul>

<script>
    const updateUrlParams = (aid) => {
        let url = new URL(window.location);
        let searchParams = url.searchParams;
        searchParams.set("aid", aid);
        window.history.replaceState({}, "", url);
    }
    const updateActiveElement = (aid) => {
        document.querySelectorAll("#items li").forEach(el => {
            if ( el.id === aid ) {
                el.dataset.active = "True"
                el.classList.add("bg-gray-300", "text-black");
            } else {                
                el.dataset.active = "False"
                el.classList.remove("bg-gray-300", "text-black");
            }
        })
    }
    document.querySelectorAll("#items li").forEach((item) => {
        item.addEventListener("click", (e) => {
            let clickedElement = e.target.closest("li");
            let aidToSelect = clickedElement.id;
            let activeElement = document.querySelector(`#items li[data-active="True"]`);
            if ( activeElement == null ) return;

            updateActiveElement(aidToSelect);
            updateUrlParams(aidToSelect);
        });
    })
</script>