Skip to content

Commit 09d68fc

Browse files
Fix array keys misalignment in form data and query (#2690)
* Fix array keys misalignment in form data and query * Refactor * Pass queryStringArrayFormat .submit() * Improve test --------- Co-authored-by: Pascal Baljet <[email protected]>
1 parent 8c8b39c commit 09d68fc

File tree

15 files changed

+258
-94
lines changed

15 files changed

+258
-94
lines changed

packages/core/src/formData.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
1-
import { FormDataConvertible } from './types'
1+
import type { FormDataConvertible, QueryStringArrayFormatOption } from './types'
22

33
export const isFormData = (value: any): value is FormData => value instanceof FormData
44

55
export function objectToFormData(
66
source: Record<string, FormDataConvertible>,
77
form: FormData = new FormData(),
88
parentKey: string | null = null,
9+
queryStringArrayFormat: QueryStringArrayFormatOption = 'brackets',
910
): FormData {
1011
source = source || {}
1112

1213
for (const key in source) {
1314
if (Object.prototype.hasOwnProperty.call(source, key)) {
14-
append(form, composeKey(parentKey, key), source[key])
15+
append(form, composeKey(parentKey, key, 'indices'), source[key], queryStringArrayFormat)
1516
}
1617
}
1718

1819
return form
1920
}
2021

21-
function composeKey(parent: string | null, key: string): string {
22-
return parent ? parent + '[' + key + ']' : key
22+
function composeKey(parent: string | null, key: string, format: QueryStringArrayFormatOption): string {
23+
if (!parent) {
24+
return key
25+
}
26+
27+
return format === 'brackets' ? `${parent}[]` : `${parent}[${key}]`
2328
}
2429

25-
function append(form: FormData, key: string, value: FormDataConvertible): void {
30+
function append(form: FormData, key: string, value: FormDataConvertible, format: QueryStringArrayFormatOption): void {
2631
if (Array.isArray(value)) {
27-
return Array.from(value.keys()).forEach((index) => append(form, composeKey(key, index.toString()), value[index]))
32+
return Array.from(value.keys()).forEach((index) =>
33+
append(form, composeKey(key, index.toString(), format), value[index], format),
34+
)
2835
} else if (value instanceof Date) {
2936
return form.append(key, value.toISOString())
3037
} else if (value instanceof File) {
@@ -41,5 +48,5 @@ function append(form: FormData, key: string, value: FormDataConvertible): void {
4148
return form.append(key, '')
4249
}
4350

44-
objectToFormData(value, form, key)
51+
objectToFormData(value, form, key, format)
4552
}

packages/core/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ export type PageHandler<ComponentType = Component> = ({
208208

209209
export type PreserveStateOption = boolean | 'errors' | ((page: Page) => boolean)
210210

211+
export type QueryStringArrayFormatOption = 'indices' | 'brackets'
212+
211213
export type Progress = AxiosProgressEvent
212214

213215
export type LocationVisit = {
@@ -231,7 +233,7 @@ export type Visit<T extends RequestPayload = RequestPayload> = {
231233
headers: Record<string, string>
232234
errorBag: string | null
233235
forceFormData: boolean
234-
queryStringArrayFormat: 'indices' | 'brackets'
236+
queryStringArrayFormat: QueryStringArrayFormatOption
235237
async: boolean
236238
showProgress: boolean
237239
prefetch: boolean

packages/core/src/url.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import * as qs from 'qs'
22
import { hasFiles } from './files'
33
import { isFormData, objectToFormData } from './formData'
4-
import { FormDataConvertible, Method, RequestPayload, UrlMethodPair, VisitOptions } from './types'
4+
import type {
5+
FormDataConvertible,
6+
Method,
7+
QueryStringArrayFormatOption,
8+
RequestPayload,
9+
UrlMethodPair,
10+
VisitOptions,
11+
} from './types'
512

613
export function hrefToUrl(href: string | URL): URL {
714
return new URL(href.toString(), typeof window === 'undefined' ? undefined : window.location.toString())
@@ -17,7 +24,7 @@ export const transformUrlAndData = (
1724
let url = typeof href === 'string' ? hrefToUrl(href) : href
1825

1926
if ((hasFiles(data) || forceFormData) && !isFormData(data)) {
20-
data = objectToFormData(data)
27+
data = objectToFormData(data, new FormData(), null, queryStringArrayFormat)
2128
}
2229

2330
if (isFormData(data)) {
@@ -36,7 +43,7 @@ export function mergeDataIntoQueryString<T extends RequestPayload>(
3643
method: Method,
3744
href: URL | string,
3845
data: T,
39-
qsArrayFormat: 'indices' | 'brackets' = 'brackets',
46+
qsArrayFormat: QueryStringArrayFormatOption = 'brackets',
4047
): [string, MergeDataIntoQueryStringDataReturnType<T>] {
4148
const hasDataForQueryString = method === 'get' && !isFormData(data) && Object.keys(data).length > 0
4249
const hasHost = urlHasProtocol(href.toString())

packages/react/src/Form.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ const Form = forwardRef<FormComponentRef, ComponentProps>(
134134

135135
const submitOptions: FormSubmitOptions = {
136136
headers,
137+
queryStringArrayFormat,
137138
errorBag,
138139
showProgress,
139140
invalidateCacheTags,

packages/react/test-app/Pages/FormComponent/Elements.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { QueryStringArrayFormatOption } from '@inertiajs/core'
12
import { Form } from '@inertiajs/react'
23

3-
export default () => {
4+
export default ({ queryStringArrayFormat }: { queryStringArrayFormat: QueryStringArrayFormatOption }) => {
45
return (
5-
<Form action="/dump/post" method="post">
6+
<Form action="/dump/post" method="post" queryStringArrayFormat={queryStringArrayFormat}>
67
{({ isDirty }) => (
78
<>
89
<h1>Form Elements</h1>

packages/react/test-app/Pages/FormComponent/Options.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Method } from '@inertiajs/core'
1+
import type { Method, QueryStringArrayFormatOption } from '@inertiajs/core'
22
import { Form } from '@inertiajs/react'
33
import { useMemo, useState } from 'react'
44
import Article from './../Article'
@@ -12,7 +12,9 @@ export default () => {
1212
const [preserveScroll, setPreserveScroll] = useState(false)
1313
const [preserveState, setPreserveState] = useState(false)
1414
const [preserveUrl, setPreserveUrl] = useState(false)
15-
const [queryStringArrayFormat, setQueryStringArrayFormat] = useState<'indices' | 'brackets' | undefined>(undefined)
15+
const [queryStringArrayFormat, setQueryStringArrayFormat] = useState<QueryStringArrayFormatOption | undefined>(
16+
undefined,
17+
)
1618

1719
function setOnly() {
1820
setOnlyValues(['users'])

packages/svelte/src/components/Form.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
8282
const submitOptions: FormSubmitOptions = {
8383
headers,
84+
queryStringArrayFormat,
8485
errorBag,
8586
showProgress,
8687
invalidateCacheTags,

packages/svelte/test-app/Pages/FormComponent/Elements.svelte

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
<script>
1+
<script lang="ts">
22
import { Form } from '@inertiajs/svelte'
3+
import type { QueryStringArrayFormatOption } from '@inertiajs/core'
4+
5+
export let queryStringArrayFormat: QueryStringArrayFormatOption
36
</script>
47

5-
<Form action="/dump/post" method="post" let:isDirty>
8+
<Form action="/dump/post" method="post" let:isDirty {queryStringArrayFormat}>
69
<h1>Form Elements</h1>
710

811
<div>

packages/svelte/test-app/Pages/FormComponent/Options.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { Form } from '@inertiajs/svelte'
3-
import type { Method } from '@inertiajs/core'
3+
import type { Method, QueryStringArrayFormatOption } from '@inertiajs/core'
44
import Article from '../Article.svelte'
55
66
let only: string[] = []
@@ -11,7 +11,7 @@
1111
let preserveScroll = false
1212
let preserveState = false
1313
let preserveUrl = false
14-
let queryStringArrayFormat: 'indices' | 'brackets' | undefined = undefined
14+
let queryStringArrayFormat: QueryStringArrayFormatOption | undefined = undefined
1515
1616
function setOnly() {
1717
only = ['users']

packages/vue3/src/form.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ const Form = defineComponent({
171171

172172
const submitOptions: FormSubmitOptions = {
173173
headers: props.headers,
174+
queryStringArrayFormat: props.queryStringArrayFormat,
174175
errorBag: props.errorBag,
175176
showProgress: props.showProgress,
176177
invalidateCacheTags: props.invalidateCacheTags,

0 commit comments

Comments
 (0)