Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GET
Submissions: don't merge into searchParams
The background --- According to the HTML Specification's [§ 4.10.21.3 Form submission algorithm][] section, submissions transmitted as `GET` requests [mutate the `[action]` URL][mutate], overriding any search parameters already encoded into the `[action]` value: > [Mutate action URL][algorithm] > --- > > Let <var>pairs</var> be the result of converting to a list of > name-value pairs with <var>entry list</var>. > > Let <var>query</var> be the result of running the > `application/x-www-form-urlencoded` serializer with <var>pairs</var> > and <var>encoding</var>. > > Set <Var>parsed action</var>'s query component to <var>query</var>. > > Plan to navigate to <var>parsed action</var>. [§ 4.10.21.3 Form submission algorithm]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm [algorithm]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-mutate-action [mutate]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm:submit-mutate-action Form submissions made with `POST` requests, on the other hand, encode _both_ the `[action]` value's query parameters and any additionally encoded body data: > [Submit as entity body][post-submit] > --- > > … > > Plan to navigate to a new request whose URL is <var>parsed > action</var>, method is <var>method</var>, header list is > « (`Content-Type`, mimeType) », and body is <var>body</var>. [post-submit]: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-body The problem --- Ever since [18585fc][] (and subsequently [hotwired#108][]), Turbo's `FetchRequest` has merged submission values from `<form>` elements with `[method="get"]` and without `[action]` attributes _into_ the current URL's query parameters. For example, consider the following forms rendered on the `/articles` page: ```html <form> <label for="q">Term</label> <input id="q" name="q"> <button>Search</button> </form> <!-- elsewhere in the page --> <form> <button name="order" value="asc">Sort ascending</button> <button name="order" value="desc">Sort ascending</button> </form> ``` Without Turbo, entering "Hotwire" as the search term into `input[name="q"]` and clicking the "Search" button would navigate the browser to `/articles?q=Hotwire`. Then, clicking the "Sort ascending" button would navigate the page to `/articles?order=asc`. Without Turbo, entering "Hotwire" as the search term into `input[name="q"]` and clicking the "Search" button navigates the browser to `/articles?q=Hotwire`. Then, clicking the "Sort ascending" button **navigates the page to `/articles?q=Hotwire&order=asc`**, effectively _merging_ values from the page's URL and the `<form>` element's fields. [18585fc]: hotwired@18585fc#diff-68b647dc2963716dc27c070f261d0b942ee9c00be7c4149ecb3a5acd94842d40R135-R142 [hotwired#108]: hotwired#108 The solution --- This commit modifies the way that `FetchRequest` constructs its `url: URL` and `body: FormData | URLSearchParams` properties. First, it _always_ assigns a `body` property, but _conditionally_ encodes that value into the `fetchOptions: RequestInit` property based on whether or not the request is an idempotent `GET`. Next, if constructed with a `body: URLSearchParams` that has entries, **replace** the `url: URL` instance's search params _entirely_ with those values, like the HTML Specification algorithm entails. If constructed with a `body: URLSearchParams` that is empty, pass the `url: URL` through and assign the property **without modifying it**. Additionally, this commit adds test cases to ensure that `POST` requests transmit data in both the body and the URL. While the previous multi-form merging behavior can be desirable, it is not the behavior outlined by the HTML Specification, so Turbo should not provide it out-of-the-box. Having said that, there are two ways for applications to restore that behavior: 1. declare a [turbo:before-fetch-request][] event listener to merge values _into_ the `FetchRequest` instance's `body`: ```js addEventListener("turbo:before-fetch-request", ({ target, detail: { fetchOptions } }) => { if (target instanceof HTMLFormElement && target.action == "get") { const searchParams = new URLSearchParams(window.location.search) for (const [ name, value ] of searchParams) { // call `set` or `append`, depending on your application's needs fetchOptions.body.set(name, value) } } }) ``` 2. declare a [formdata][] event listener to merge values _into_ the submitted form's [FormData][] instance prior to entering the Turbo request pipeline: ```js addEventListener("formdata", ({ target, formData }) => { if (target.action == "get") { const searchParams = new URLSearchParams(window.location.search) for (const [ name, value ] of searchParams) { // call `set` or `append`, depending on your application's needs formData.set(name, value) } } }) ``` [turbo:before-fetch-request]: https://turbo.hotwired.dev/reference/events [formdata]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/formdata_event [FormData]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
- Loading branch information