JavaScript hooks and debugging
Flux is deliberately small, but it still needs to cooperate with any JavaScript we add ourselves.
Event listeners are reattached after swaps
Internally, Flux wraps addEventListener so it can keep a record of listeners attached to elements. When a Flux update replaces an element, Flux reattaches those listeners to the matching new element.
That means code like this continues to work after a refresh:
const button = document.querySelector("button.tracker");
button.addEventListener("click", () => {
console.log("tracked");
});
As long as the new document contains the corresponding replacement element, Flux carries the listener over.
New Flux elements are initialised automatically
If an update inserts new elements containing data-flux, Flux scans and initialises them as part of the replacement process. This is particularly important for:
- nested submit buttons
- new Flux links
- live regions introduced by an update
Listening to Flux lifecycle events
Flux dispatches a few document-level events so application JavaScript can cooperate with the update flow without taking over rendering.
Before a request
flux:before-request fires just before Flux calls fetch.
document.addEventListener("flux:before-request", event => {
event.detail.requestOptions.headers = new Headers(event.detail.requestOptions.headers);
event.detail.requestOptions.headers.set("X-Requested-With", "Flux");
});
The event detail contains:
url- the URL Flux is about to fetchrequestOptions- the options that will be passed tofetchmethod- the request method, normalised to lower casehistoryState- Flux’s internal navigation state for this request
Both url and requestOptions are mutable. That makes this event useful for adding headers, appending query parameters, or passing lightweight client state back to the server.
Before and after rendering
flux:before-render fires after Flux has worked out which elements will update, but before it changes the DOM.
flux:after-render fires after those updates have been applied.
document.addEventListener("flux:before-render", event => {
for(const update of event.detail.updates) {
console.log("Will update", update.existingElement, "with", update.newElement);
}
});
document.addEventListener("flux:after-render", event => {
for(const update of event.detail.updates) {
console.log("Updated element", update.element);
}
});
Each update record contains:
type- the Flux update type, such asouter,inner, orlive-outermode- the broad update mode, such asouter,inner, orattributesexistingElement- the element currently in the documentnewElement- the matching element from the returned documentelement- the element that remains in the document after rendering, available influx:after-render
These hooks are a good fit for small behaviours such as preserving scroll position, recording which item was last seen, or adding request metadata. They are not intended to replace Flux’s server-rendered update model.
Enable debug logging
When we are working on Flux itself, or trying to understand a tricky page, we can enable debug mode:
import { FluxDebug } from "@phpgt/flux";
import "@phpgt/flux";
With debug mode enabled, Flux logs extra detail about event registration, target storage, and focus restoration.
Failure behaviour
If Flux cannot process a returned document properly, it logs an error and reloads the page. The most common reason would be a response that is not a complete HTML document.
[!IMPORTANT] Flux is designed around full HTML responses. If we return a fragment with no
<head>, Flux treats that as an error and falls back to a normal reload.
Testing the behaviour
This repository includes two useful test layers:
vitestunit tests for the JavaScript classes insrc/behatbrowser tests for the runnable examples inexample/
The browser suite is the best source of truth for end-to-end behaviour because it exercises the real DOM update flow.
The walkthrough is complete. See the list of flux attributes as the reference sheet, or browse the runnable examples.