htmx icon indicating copy to clipboard operation
htmx copied to clipboard

When using HX-Redirect, the htmx:afterRequest event's detail.successful and detail.failed fields are undefined

Open luontola opened this issue 1 year ago • 4 comments

When the server returns a HX-Redirect header, the htmx:afterRequest event for that request will not tell whether the request was successful or failed. The event.detail.successful and event.detail.failed are both undefined.

The expected behavior would be that the successful and failed fields will always be present.

On related note, the fields are also missing if the browser is in offline mode. I think those too should be categorized as failed requests.

This happens with htmx 1.9.12 and 2.0.0-beta3

Reproducing

from flask import Flask, Response, request

app = Flask(__name__)


@app.route("/")
def home():
    return """
<script src="https://unpkg.com/[email protected]"></script>
<script type="module">
document.body.addEventListener('htmx:afterRequest', event => {
    console.log(event);
    console.log('status: ' + event.detail.xhr.status);
    console.log('successful: ' + event.detail.successful);
    console.log('failed: ' + event.detail.failed);
    // Some error handling code:
    if (event.detail.successful) {
        console.log('Request successful.');
    } else if (event.detail.failed) {
        console.log('Request failed.');
        alert('Request failed.');
    } else {
        console.log('Unknown error. Probably network is down.');
        alert('Unknown error. Probably network is down.');
    }
});
</script>

<h1>Home</h1>
<p><button hx-get="/clicked?scenario=1" hx-swap="outerHTML">Button 1: successful</button></p>
<p><button hx-get="/clicked?scenario=2" hx-swap="outerHTML">Button 2: error from backend</button></p>
<p><button hx-get="/clicked?scenario=3" hx-swap="outerHTML">Button 3: hx-redirect (BUG: thought to be a network error)</button></p>
<p><a href="/">Restart</a></p>
"""


@app.route("/clicked")
def clicked():
    resp = Response("<h3>Clicked</h3>")
    scenario = request.args.get('scenario')
    if scenario == "1":
        resp.status = 200
    if scenario == "2":
        resp.status = 400
    if scenario == "3":
        # BUG: If the HX-Redirect header is present, the successful/failed fields will be undefined.
        resp.status = 200
        resp.headers["HX-Redirect"] = "/redirected"
    return resp


@app.route("/redirected")
def redirected():
    return """
<h1>Redirected</h1>
<p><a href="/">Restart</a></p>
"""

Run the above app with the commands:

pip install Flask
flask --app server.py --debug run

Visit http://127.0.0.1:5000/ and click each of the buttons while observing what is printed to the console.

Buttons 1 and 2 work correctly.

But when you click Button 3, the htmx:afterRequest event will be missing the event.detail.successful and event.detail.failed fields. The app will print the following to the console and its error handler will show an error popup. (In a real app that doesn't use alert(), the error message would flash for a fraction of a second before redirecting to the next page).

status: 200
successful: undefined
failed: undefined

Those fields are also missing when the browser is in offline mode. (This article told to use that to detect network errors. I'm not sure if that is expected behavior.)

Set the browser to offline mode in the Chrome DevTool's Network tab and click any of the three buttons to see that behavior.

Workaround

Ignore the event.detail.successful and event.detail.failed fields, and instead check the HTTP status code in event.detail.xhr.status yourself.

luontola avatar May 01 '24 13:05 luontola

Just observed same thing happening with HX-Location, on successful response event.detail.successful is missing

Lanayx avatar Jul 05 '24 12:07 Lanayx

This is frustrating since looking for the successful response was one solution to #1925 (Keep hmtx-request class while redirecting). Should a redirect header not be a successful response?

dwasyl avatar Jul 17 '24 00:07 dwasyl

Just bit me too :)

james-hill avatar Mar 04 '25 23:03 james-hill