Skip to content

Commit

Permalink
feat(form): adding support for nested paths
Browse files Browse the repository at this point in the history
  • Loading branch information
joaopalopes24 committed Feb 3, 2025
1 parent fc8e1d4 commit 69a0f03
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 81 deletions.
4 changes: 4 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type FormDataConvertible =
| null
| undefined

export type FormDataKeys<T> = {
[K in keyof T & string]: T[K] extends object ? (T[K] extends Array<any> ? K : `${K}.${FormDataKeys<T[K]>}` | K) : K
}[keyof T & string]

export type Method = 'get' | 'post' | 'put' | 'patch' | 'delete'

export type RequestPayload = Record<string, FormDataConvertible> | FormData
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@
},
"dependencies": {
"@inertiajs/core": "2.0.3",
"lodash.isequal": "^4.5.0"
"lodash": "^4.5.0"
}
}
56 changes: 28 additions & 28 deletions packages/react/src/useForm.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { FormDataConvertible, Method, Progress, router, VisitOptions } from '@inertiajs/core'
import isEqual from 'lodash.isequal'
import { FormDataConvertible, FormDataKeys, Method, Progress, router, VisitOptions } from '@inertiajs/core'
import { cloneDeep, get, has, isEqual, set } from 'lodash'
import { useCallback, useEffect, useRef, useState } from 'react'
import useRemember from './useRemember'

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

export interface InertiaFormProps<TForm extends FormDataType> {
data: TForm
isDirty: boolean
errors: Partial<Record<keyof TForm, string>>
errors: Partial<Record<FormDataKeys<TForm>, string>>
hasErrors: boolean
processing: boolean
progress: Progress | null
Expand All @@ -21,12 +21,12 @@ export interface InertiaFormProps<TForm extends FormDataType> {
setData: setDataByObject<TForm> & setDataByMethod<TForm> & setDataByKeyValuePair<TForm>
transform: (callback: (data: TForm) => object) => void
setDefaults(): void
setDefaults(field: keyof TForm, value: FormDataConvertible): void
setDefaults(field: FormDataKeys<TForm>, value: FormDataConvertible): void
setDefaults(fields: Partial<TForm>): void
reset: (...fields: (keyof TForm)[]) => void
clearErrors: (...fields: (keyof TForm)[]) => void
setError(field: keyof TForm, value: string): void
setError(errors: Record<keyof TForm, string>): void
reset: (...fields: FormDataKeys<TForm>[]) => void
clearErrors: (...fields: FormDataKeys<TForm>[]) => void
setError(field: FormDataKeys<TForm>, value: string): void
setError(errors: Record<FormDataKeys<TForm>, string>): void
submit: (method: Method, url: string, options?: FormOptions) => void
get: (url: string, options?: FormOptions) => void
patch: (url: string, options?: FormOptions) => void
Expand All @@ -53,8 +53,8 @@ export default function useForm<TForm extends FormDataType>(
const recentlySuccessfulTimeoutId = useRef(null)
const [data, setData] = rememberKey ? useRemember(defaults, `${rememberKey}:data`) : useState(defaults)
const [errors, setErrors] = rememberKey
? useRemember({} as Partial<Record<keyof TForm, string>>, `${rememberKey}:errors`)
: useState({} as Partial<Record<keyof TForm, string>>)
? useRemember({} as Partial<Record<FormDataKeys<TForm>, string>>, `${rememberKey}:errors`)
: useState({} as Partial<Record<FormDataKeys<TForm>, string>>)
const [hasErrors, setHasErrors] = useState(false)
const [processing, setProcessing] = useState(false)
const [progress, setProgress] = useState(null)
Expand Down Expand Up @@ -168,9 +168,9 @@ export default function useForm<TForm extends FormDataType>(
)

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

const setDefaultsFunction = useCallback(
(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) => {
(fieldOrFields?: FormDataKeys<TForm> | Partial<TForm>, maybeValue?: FormDataConvertible) => {
if (typeof fieldOrFields === 'undefined') {
setDefaults(() => data)
} else {
setDefaults((defaults) => ({
...defaults,
...(typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : (fieldOrFields as TForm)),
}))
setDefaults((defaults) => {
return typeof fieldOrFields === 'string'
? set(cloneDeep(defaults), fieldOrFields, maybeValue)
: Object.assign(cloneDeep(defaults), fieldOrFields)
})
}
},
[data, setDefaults],
Expand All @@ -200,14 +201,13 @@ export default function useForm<TForm extends FormDataType>(
setData(defaults)
} else {
setData((data) =>
(Object.keys(defaults) as Array<keyof TForm>)
.filter((key) => fields.includes(key))
(fields as Array<FormDataKeys<TForm>>)
.filter((key) => has(defaults, key))
.reduce(
(carry, key) => {
carry[key] = defaults[key]
return carry
return set(carry, key, get(defaults, key))
},
{ ...data },
{ ...data } as TForm,
),
)
}
Expand All @@ -216,13 +216,13 @@ export default function useForm<TForm extends FormDataType>(
)

const setError = useCallback(
(fieldOrFields: keyof TForm | Record<keyof TForm, string>, maybeValue?: string) => {
(fieldOrFields: FormDataKeys<TForm> | Record<FormDataKeys<TForm>, string>, maybeValue?: string) => {
setErrors((errors) => {
const newErrors = {
...errors,
...(typeof fieldOrFields === 'string'
? { [fieldOrFields]: maybeValue }
: (fieldOrFields as Record<keyof TForm, string>)),
: (fieldOrFields as Record<FormDataKeys<TForm>, string>)),
}
setHasErrors(Object.keys(newErrors).length > 0)
return newErrors
Expand All @@ -234,7 +234,7 @@ export default function useForm<TForm extends FormDataType>(
const clearErrors = useCallback(
(...fields) => {
setErrors((errors) => {
const newErrors = (Object.keys(errors) as Array<keyof TForm>).reduce(
const newErrors = (Object.keys(errors) as Array<FormDataKeys<TForm>>).reduce(
(carry, field) => ({
...carry,
...(fields.length > 0 && !fields.includes(field) ? { [field]: errors[field] } : {}),
Expand All @@ -251,7 +251,7 @@ export default function useForm<TForm extends FormDataType>(
const createSubmitMethod = (method) => (url, options) => {
submit(method, url, options)
}
const get = useCallback(createSubmitMethod('get'), [submit])
const getMethod = useCallback(createSubmitMethod('get'), [submit])
const post = useCallback(createSubmitMethod('post'), [submit])
const put = useCallback(createSubmitMethod('put'), [submit])
const patch = useCallback(createSubmitMethod('patch'), [submit])
Expand Down Expand Up @@ -283,7 +283,7 @@ export default function useForm<TForm extends FormDataType>(
setError,
clearErrors,
submit,
get,
get: getMethod,
post,
put,
patch,
Expand Down
51 changes: 25 additions & 26 deletions packages/svelte/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ActiveVisit,
Errors,
FormDataConvertible,
FormDataKeys,
Method,
Page,
PendingVisit,
Expand All @@ -11,31 +12,30 @@ import type {
} from '@inertiajs/core'
import { router } from '@inertiajs/core'
import type { AxiosProgressEvent } from 'axios'
import cloneDeep from 'lodash/cloneDeep'
import isEqual from 'lodash/isEqual'
import { cloneDeep, get, has, isEqual, set } from 'lodash'
import { writable, type Writable } from 'svelte/store'

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

export interface InertiaFormProps<TForm extends FormDataType> {
isDirty: boolean
errors: Partial<Record<keyof TForm, string>>
errors: Partial<Record<FormDataKeys<TForm>, string>>
hasErrors: boolean
progress: Progress | null
wasSuccessful: boolean
recentlySuccessful: boolean
processing: boolean
setStore(data: TForm): void
setStore(key: keyof TForm, value?: FormDataConvertible): void
setStore(key: FormDataKeys<TForm>, value?: FormDataConvertible): void
data(): TForm
transform(callback: (data: TForm) => object): this
defaults(): this
defaults(fields: Partial<TForm>): this
defaults(field?: keyof TForm, value?: FormDataConvertible): this
reset(...fields: (keyof TForm)[]): this
clearErrors(...fields: (keyof TForm)[]): this
setError(field: keyof TForm, value: string): this
defaults(field?: FormDataKeys<TForm>, value?: FormDataConvertible): this
reset(...fields: FormDataKeys<TForm>[]): this
clearErrors(...fields: FormDataKeys<TForm>[]): this
setError(field: FormDataKeys<TForm>, value: string): this
setError(errors: Errors): this
submit(method: Method, url: string, options?: FormOptions): void
get(url: string, options?: FormOptions): void
Expand All @@ -61,7 +61,7 @@ export default function useForm<TForm extends FormDataType>(
const inputData = (typeof rememberKeyOrData === 'string' ? maybeData : rememberKeyOrData) ?? {}
const data: TForm = typeof inputData === 'function' ? inputData() : (inputData as TForm)
const restored = rememberKey
? (router.restore(rememberKey) as { data: TForm; errors: Record<keyof TForm, string> } | null)
? (router.restore(rememberKey) as { data: TForm; errors: Record<FormDataKeys<TForm>, string> } | null)
: null
let defaults = cloneDeep(data)
let cancelToken: { cancel: () => void } | null = null
Expand All @@ -79,27 +79,27 @@ export default function useForm<TForm extends FormDataType>(
processing: false,
setStore(keyOrData, maybeValue = undefined) {
store.update((store) => {
return Object.assign(store, typeof keyOrData === 'string' ? { [keyOrData]: maybeValue } : keyOrData)
return typeof keyOrData === 'string' ? set(store, keyOrData, maybeValue) : Object.assign(store, keyOrData)
})
},
data() {
return Object.keys(data).reduce((carry, key) => {
carry[key] = this[key]
return carry
return set(carry, key, get(this, key))
}, {} as FormDataType) as TForm
},
transform(callback) {
transform = callback
return this
},
defaults(fieldOrFields?: keyof TForm | Partial<TForm>, maybeValue?: FormDataConvertible) {
defaults =
typeof fieldOrFields === 'undefined'
? cloneDeep(this.data())
: Object.assign(
cloneDeep(defaults),
typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields,
)
defaults(fieldOrFields?: FormDataKeys<TForm> | Partial<TForm>, maybeValue?: FormDataConvertible) {
if (typeof fieldOrFields === 'undefined') {
defaults = cloneDeep(this.data())
} else {
defaults =
typeof fieldOrFields === 'string'
? set(cloneDeep(defaults), fieldOrFields, maybeValue)
: Object.assign(cloneDeep(defaults), fieldOrFields)
}

return this
},
Expand All @@ -109,18 +109,17 @@ export default function useForm<TForm extends FormDataType>(
this.setStore(clonedData)
} else {
this.setStore(
Object.keys(clonedData)
.filter((key) => fields.includes(key))
(fields as Array<FormDataKeys<TForm>>)
.filter((key) => has(clonedData, key))
.reduce((carry, key) => {
carry[key] = clonedData[key]
return carry
return set(carry, key, get(clonedData, key))
}, {} as FormDataType) as TForm,
)
}

return this
},
setError(fieldOrFields: keyof TForm | Errors, maybeValue?: string) {
setError(fieldOrFields: FormDataKeys<TForm> | Errors, maybeValue?: string) {
this.setStore('errors', {
...this.errors,
...((typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields) as Errors),
Expand All @@ -131,7 +130,7 @@ export default function useForm<TForm extends FormDataType>(
clearErrors(...fields) {
this.setStore(
'errors',
Object.keys(this.errors).reduce(
(Object.keys(this.errors) as FormDataKeys<TForm>[]).reduce(
(carry, field) => ({
...carry,
...(fields.length > 0 && !fields.includes(field) ? { [field]: this.errors[field] } : {}),
Expand Down
3 changes: 1 addition & 2 deletions packages/vue3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
},
"dependencies": {
"@inertiajs/core": "2.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
"lodash": "^4.5.0"
}
}
Loading

0 comments on commit 69a0f03

Please sign in to comment.