Skip to content

Commit 69a0f03

Browse files
committed
feat(form): adding support for nested paths
1 parent fc8e1d4 commit 69a0f03

File tree

6 files changed

+80
-81
lines changed

6 files changed

+80
-81
lines changed

packages/core/src/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export type FormDataConvertible =
2121
| null
2222
| undefined
2323

24+
export type FormDataKeys<T> = {
25+
[K in keyof T & string]: T[K] extends object ? (T[K] extends Array<any> ? K : `${K}.${FormDataKeys<T[K]>}` | K) : K
26+
}[keyof T & string]
27+
2428
export type Method = 'get' | 'post' | 'put' | 'patch' | 'delete'
2529

2630
export type RequestPayload = Record<string, FormDataConvertible> | FormData

packages/react/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@
6060
},
6161
"dependencies": {
6262
"@inertiajs/core": "2.0.3",
63-
"lodash.isequal": "^4.5.0"
63+
"lodash": "^4.5.0"
6464
}
6565
}

packages/react/src/useForm.ts

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
import { FormDataConvertible, Method, Progress, router, VisitOptions } from '@inertiajs/core'
2-
import isEqual from 'lodash.isequal'
1+
import { FormDataConvertible, FormDataKeys, Method, Progress, router, VisitOptions } from '@inertiajs/core'
2+
import { cloneDeep, get, has, isEqual, set } from 'lodash'
33
import { useCallback, useEffect, useRef, useState } from 'react'
44
import useRemember from './useRemember'
55

66
type setDataByObject<TForm> = (data: TForm) => void
77
type setDataByMethod<TForm> = (data: (previousData: TForm) => TForm) => void
8-
type setDataByKeyValuePair<TForm> = <K extends keyof TForm>(key: K, value: TForm[K]) => void
8+
type setDataByKeyValuePair<TForm> = (key: FormDataKeys<TForm>, value: any) => void
99
type FormDataType = Record<string, FormDataConvertible>
1010
type FormOptions = Omit<VisitOptions, 'data'>
1111

1212
export interface InertiaFormProps<TForm extends FormDataType> {
1313
data: TForm
1414
isDirty: boolean
15-
errors: Partial<Record<keyof TForm, string>>
15+
errors: Partial<Record<FormDataKeys<TForm>, string>>
1616
hasErrors: boolean
1717
processing: boolean
1818
progress: Progress | null
@@ -21,12 +21,12 @@ export interface InertiaFormProps<TForm extends FormDataType> {
2121
setData: setDataByObject<TForm> & setDataByMethod<TForm> & setDataByKeyValuePair<TForm>
2222
transform: (callback: (data: TForm) => object) => void
2323
setDefaults(): void
24-
setDefaults(field: keyof TForm, value: FormDataConvertible): void
24+
setDefaults(field: FormDataKeys<TForm>, value: FormDataConvertible): void
2525
setDefaults(fields: Partial<TForm>): void
26-
reset: (...fields: (keyof TForm)[]) => void
27-
clearErrors: (...fields: (keyof TForm)[]) => void
28-
setError(field: keyof TForm, value: string): void
29-
setError(errors: Record<keyof TForm, string>): void
26+
reset: (...fields: FormDataKeys<TForm>[]) => void
27+
clearErrors: (...fields: FormDataKeys<TForm>[]) => void
28+
setError(field: FormDataKeys<TForm>, value: string): void
29+
setError(errors: Record<FormDataKeys<TForm>, string>): void
3030
submit: (method: Method, url: string, options?: FormOptions) => void
3131
get: (url: string, options?: FormOptions) => void
3232
patch: (url: string, options?: FormOptions) => void
@@ -53,8 +53,8 @@ export default function useForm<TForm extends FormDataType>(
5353
const recentlySuccessfulTimeoutId = useRef(null)
5454
const [data, setData] = rememberKey ? useRemember(defaults, `${rememberKey}:data`) : useState(defaults)
5555
const [errors, setErrors] = rememberKey
56-
? useRemember({} as Partial<Record<keyof TForm, string>>, `${rememberKey}:errors`)
57-
: useState({} as Partial<Record<keyof TForm, string>>)
56+
? useRemember({} as Partial<Record<FormDataKeys<TForm>, string>>, `${rememberKey}:errors`)
57+
: useState({} as Partial<Record<FormDataKeys<TForm>, string>>)
5858
const [hasErrors, setHasErrors] = useState(false)
5959
const [processing, setProcessing] = useState(false)
6060
const [progress, setProgress] = useState(null)
@@ -168,9 +168,9 @@ export default function useForm<TForm extends FormDataType>(
168168
)
169169

170170
const setDataFunction = useCallback(
171-
(keyOrData: keyof TForm | Function | TForm, maybeValue?: TForm[keyof TForm]) => {
171+
(keyOrData: FormDataKeys<TForm> | Function | TForm, maybeValue?: any) => {
172172
if (typeof keyOrData === 'string') {
173-
setData((data) => ({ ...data, [keyOrData]: maybeValue }))
173+
setData((data) => set(cloneDeep(data), keyOrData, maybeValue))
174174
} else if (typeof keyOrData === 'function') {
175175
setData((data) => keyOrData(data))
176176
} else {
@@ -181,14 +181,15 @@ export default function useForm<TForm extends FormDataType>(
181181
)
182182

183183
const setDefaultsFunction = useCallback(
184-
(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) => {
184+
(fieldOrFields?: FormDataKeys<TForm> | Partial<TForm>, maybeValue?: FormDataConvertible) => {
185185
if (typeof fieldOrFields === 'undefined') {
186186
setDefaults(() => data)
187187
} else {
188-
setDefaults((defaults) => ({
189-
...defaults,
190-
...(typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : (fieldOrFields as TForm)),
191-
}))
188+
setDefaults((defaults) => {
189+
return typeof fieldOrFields === 'string'
190+
? set(cloneDeep(defaults), fieldOrFields, maybeValue)
191+
: Object.assign(cloneDeep(defaults), fieldOrFields)
192+
})
192193
}
193194
},
194195
[data, setDefaults],
@@ -200,14 +201,13 @@ export default function useForm<TForm extends FormDataType>(
200201
setData(defaults)
201202
} else {
202203
setData((data) =>
203-
(Object.keys(defaults) as Array<keyof TForm>)
204-
.filter((key) => fields.includes(key))
204+
(fields as Array<FormDataKeys<TForm>>)
205+
.filter((key) => has(defaults, key))
205206
.reduce(
206207
(carry, key) => {
207-
carry[key] = defaults[key]
208-
return carry
208+
return set(carry, key, get(defaults, key))
209209
},
210-
{ ...data },
210+
{ ...data } as TForm,
211211
),
212212
)
213213
}
@@ -216,13 +216,13 @@ export default function useForm<TForm extends FormDataType>(
216216
)
217217

218218
const setError = useCallback(
219-
(fieldOrFields: keyof TForm | Record<keyof TForm, string>, maybeValue?: string) => {
219+
(fieldOrFields: FormDataKeys<TForm> | Record<FormDataKeys<TForm>, string>, maybeValue?: string) => {
220220
setErrors((errors) => {
221221
const newErrors = {
222222
...errors,
223223
...(typeof fieldOrFields === 'string'
224224
? { [fieldOrFields]: maybeValue }
225-
: (fieldOrFields as Record<keyof TForm, string>)),
225+
: (fieldOrFields as Record<FormDataKeys<TForm>, string>)),
226226
}
227227
setHasErrors(Object.keys(newErrors).length > 0)
228228
return newErrors
@@ -234,7 +234,7 @@ export default function useForm<TForm extends FormDataType>(
234234
const clearErrors = useCallback(
235235
(...fields) => {
236236
setErrors((errors) => {
237-
const newErrors = (Object.keys(errors) as Array<keyof TForm>).reduce(
237+
const newErrors = (Object.keys(errors) as Array<FormDataKeys<TForm>>).reduce(
238238
(carry, field) => ({
239239
...carry,
240240
...(fields.length > 0 && !fields.includes(field) ? { [field]: errors[field] } : {}),
@@ -251,7 +251,7 @@ export default function useForm<TForm extends FormDataType>(
251251
const createSubmitMethod = (method) => (url, options) => {
252252
submit(method, url, options)
253253
}
254-
const get = useCallback(createSubmitMethod('get'), [submit])
254+
const getMethod = useCallback(createSubmitMethod('get'), [submit])
255255
const post = useCallback(createSubmitMethod('post'), [submit])
256256
const put = useCallback(createSubmitMethod('put'), [submit])
257257
const patch = useCallback(createSubmitMethod('patch'), [submit])
@@ -283,7 +283,7 @@ export default function useForm<TForm extends FormDataType>(
283283
setError,
284284
clearErrors,
285285
submit,
286-
get,
286+
get: getMethod,
287287
post,
288288
put,
289289
patch,

packages/svelte/src/useForm.ts

+25-26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ActiveVisit,
33
Errors,
44
FormDataConvertible,
5+
FormDataKeys,
56
Method,
67
Page,
78
PendingVisit,
@@ -11,31 +12,30 @@ import type {
1112
} from '@inertiajs/core'
1213
import { router } from '@inertiajs/core'
1314
import type { AxiosProgressEvent } from 'axios'
14-
import cloneDeep from 'lodash/cloneDeep'
15-
import isEqual from 'lodash/isEqual'
15+
import { cloneDeep, get, has, isEqual, set } from 'lodash'
1616
import { writable, type Writable } from 'svelte/store'
1717

1818
type FormDataType = Record<string, FormDataConvertible>
1919
type FormOptions = Omit<VisitOptions, 'data'>
2020

2121
export interface InertiaFormProps<TForm extends FormDataType> {
2222
isDirty: boolean
23-
errors: Partial<Record<keyof TForm, string>>
23+
errors: Partial<Record<FormDataKeys<TForm>, string>>
2424
hasErrors: boolean
2525
progress: Progress | null
2626
wasSuccessful: boolean
2727
recentlySuccessful: boolean
2828
processing: boolean
2929
setStore(data: TForm): void
30-
setStore(key: keyof TForm, value?: FormDataConvertible): void
30+
setStore(key: FormDataKeys<TForm>, value?: FormDataConvertible): void
3131
data(): TForm
3232
transform(callback: (data: TForm) => object): this
3333
defaults(): this
3434
defaults(fields: Partial<TForm>): this
35-
defaults(field?: keyof TForm, value?: FormDataConvertible): this
36-
reset(...fields: (keyof TForm)[]): this
37-
clearErrors(...fields: (keyof TForm)[]): this
38-
setError(field: keyof TForm, value: string): this
35+
defaults(field?: FormDataKeys<TForm>, value?: FormDataConvertible): this
36+
reset(...fields: FormDataKeys<TForm>[]): this
37+
clearErrors(...fields: FormDataKeys<TForm>[]): this
38+
setError(field: FormDataKeys<TForm>, value: string): this
3939
setError(errors: Errors): this
4040
submit(method: Method, url: string, options?: FormOptions): void
4141
get(url: string, options?: FormOptions): void
@@ -61,7 +61,7 @@ export default function useForm<TForm extends FormDataType>(
6161
const inputData = (typeof rememberKeyOrData === 'string' ? maybeData : rememberKeyOrData) ?? {}
6262
const data: TForm = typeof inputData === 'function' ? inputData() : (inputData as TForm)
6363
const restored = rememberKey
64-
? (router.restore(rememberKey) as { data: TForm; errors: Record<keyof TForm, string> } | null)
64+
? (router.restore(rememberKey) as { data: TForm; errors: Record<FormDataKeys<TForm>, string> } | null)
6565
: null
6666
let defaults = cloneDeep(data)
6767
let cancelToken: { cancel: () => void } | null = null
@@ -79,27 +79,27 @@ export default function useForm<TForm extends FormDataType>(
7979
processing: false,
8080
setStore(keyOrData, maybeValue = undefined) {
8181
store.update((store) => {
82-
return Object.assign(store, typeof keyOrData === 'string' ? { [keyOrData]: maybeValue } : keyOrData)
82+
return typeof keyOrData === 'string' ? set(store, keyOrData, maybeValue) : Object.assign(store, keyOrData)
8383
})
8484
},
8585
data() {
8686
return Object.keys(data).reduce((carry, key) => {
87-
carry[key] = this[key]
88-
return carry
87+
return set(carry, key, get(this, key))
8988
}, {} as FormDataType) as TForm
9089
},
9190
transform(callback) {
9291
transform = callback
9392
return this
9493
},
95-
defaults(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) {
96-
defaults =
97-
typeof fieldOrFields === 'undefined'
98-
? cloneDeep(this.data())
99-
: Object.assign(
100-
cloneDeep(defaults),
101-
typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields,
102-
)
94+
defaults(fieldOrFields?: FormDataKeys<TForm> | Partial<TForm>, maybeValue?: FormDataConvertible) {
95+
if (typeof fieldOrFields === 'undefined') {
96+
defaults = cloneDeep(this.data())
97+
} else {
98+
defaults =
99+
typeof fieldOrFields === 'string'
100+
? set(cloneDeep(defaults), fieldOrFields, maybeValue)
101+
: Object.assign(cloneDeep(defaults), fieldOrFields)
102+
}
103103

104104
return this
105105
},
@@ -109,18 +109,17 @@ export default function useForm<TForm extends FormDataType>(
109109
this.setStore(clonedData)
110110
} else {
111111
this.setStore(
112-
Object.keys(clonedData)
113-
.filter((key) => fields.includes(key))
112+
(fields as Array<FormDataKeys<TForm>>)
113+
.filter((key) => has(clonedData, key))
114114
.reduce((carry, key) => {
115-
carry[key] = clonedData[key]
116-
return carry
115+
return set(carry, key, get(clonedData, key))
117116
}, {} as FormDataType) as TForm,
118117
)
119118
}
120119

121120
return this
122121
},
123-
setError(fieldOrFields: keyof TForm | Errors, maybeValue?: string) {
122+
setError(fieldOrFields: FormDataKeys<TForm> | Errors, maybeValue?: string) {
124123
this.setStore('errors', {
125124
...this.errors,
126125
...((typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields) as Errors),
@@ -131,7 +130,7 @@ export default function useForm<TForm extends FormDataType>(
131130
clearErrors(...fields) {
132131
this.setStore(
133132
'errors',
134-
Object.keys(this.errors).reduce(
133+
(Object.keys(this.errors) as FormDataKeys<TForm>[]).reduce(
135134
(carry, field) => ({
136135
...carry,
137136
...(fields.length > 0 && !fields.includes(field) ? { [field]: this.errors[field] } : {}),

packages/vue3/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
},
6060
"dependencies": {
6161
"@inertiajs/core": "2.0.3",
62-
"lodash.clonedeep": "^4.5.0",
63-
"lodash.isequal": "^4.5.0"
62+
"lodash": "^4.5.0"
6463
}
6564
}

0 commit comments

Comments
 (0)