Skip to content

Fix reset modifier timing and form lookup - #3981

Closed
1cg wants to merge 1 commit into
four-devfrom
fix/reset-form-modifier
Closed

Fix reset modifier timing and form lookup#3981
1cg wants to merge 1 commit into
four-devfrom
fix/reset-form-modifier

Conversation

@1cg

@1cg 1cg commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #3977.

Problem

The reset trigger modifier ran in the trigger listener, before htmx collected the request body. Three defects:

  1. The request sent empty values. The form reset before __handleTriggerEvent collected the body, so a <form hx-post hx-trigger="submit reset"> sent msg= instead of the value the user typed.
  2. A cancelled hx-confirm still reset the form. The reset ran before the confirm gate.
  3. elt.closest('form') ignored form="id". An element that points at a form from outside it did not reset that form.

Changes

src/htmx.js:

  • inner no longer resets. It passes the firing spec to the handler.
  • __createHtmxEventHandler records ctx.reset from that spec.
  • The reset moved to __issueRequest, after the confirm gate. It runs only when the request is committed, and after the body is collected.
  • Both the trigger and swap paths use elt.form || elt.closest('form').

Tests: the two tests from #3977 asserted a synchronous reset, which no longer holds. Rewrote them and added four for the sent body, per-spec attribution, cancelled confirm, and form="id".

Docs corrected on both reference pages. The old text promised a reset "before the request is sent", which was the bug.

1735 passed, 0 failed, 100 percent coverage.

Open question

Not super happy with this, seems hard to get trigger info to the right spot. Maybe drop in favor of an hx-reset extension?

The spec is known in the trigger listener, but the reset must happen much later. The options I looked at:

  • Extra handler param (this PR). Additive. onTrigger(elt, spec, handler) is internal API used by hx-ws, hx-sse, and hx-multipart, and all of them ignore the second arg.
  • Handler factory. Cleaner in isolation, but silently breaks those three extensions.
  • Stash the spec on the event. detail is a read-only number on UIEvent, so there is nowhere to put it on a click. Also racy, because from: fans one event to several specs and the read happens after an await.

Marked as a draft while we decide.

@1cg

1cg commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Not super happy with this, seems hard to get trigger info to the right spot. Maybe drop in favor of an hx-reset extension?

@MichaelWest22

Copy link
Copy Markdown
Collaborator

yeah we could simplify it a little maybe with

                 let inner = (evt) => {
                    if (spec.halt || spec.prevent) evt.preventDefault();
                    if (spec.halt || spec.stop || spec.consume) evt.stopPropagation();
                    if (spec.once) {
                        for (let info of spec.listeners) info.fromElt.removeEventListener(info.eventName, info.handler, info);
                    }
                    evt._htmxTriggerSpec = spec;  <- store the spec here on the evt so ctx.sourceEvent._htmxTriggerSpec works
                    handler(evt);
                };

and if we did just this change then an extension can pickup custom trigger specs like reset and use them properly instead of guessing if the triggering event was the one that triggered the request we need to reset.

htmx.registerExtension('hx-reset', {
    htmx_before_request(elt, detail) {
        let spec = detail.ctx.sourceEvent._htmxTriggerSpec;
        if (spec?.reset) {
            (elt.form || elt.closest?.('form'))?.reset();
        }
    },

    htmx_after_settle(elt, detail) {
        if (detail.task.swapSpec?.reset) {
            let src = detail.task.sourceElement;
            (src?.form || src?.closest?.('form'))?.reset();
        }
    }
});

@1cg

1cg commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Closing. #3977 was reverted in 9f7a40f, so this patches code that is no longer on four-dev. Revisit after 4.0.0, likely as an hx-reset extension.

@1cg 1cg closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants