diff --git a/packages/react/src/useForm.ts b/packages/react/src/useForm.ts index 53a58984a..780932838 100644 --- a/packages/react/src/useForm.ts +++ b/packages/react/src/useForm.ts @@ -3,6 +3,14 @@ import isEqual from 'lodash.isequal' import { useCallback, useEffect, useRef, useState } from 'react' import useRemember from './useRemember' +type FormDataKeys = { + [K in keyof T & string]: T[K] extends object + ? T[K] extends Array + ? K + : `${K}.${FormDataKeys}` | K + : K; +}[keyof T & string]; + type setDataByObject = (data: TForm) => void type setDataByMethod = (data: (previousData: TForm) => TForm) => void type setDataByKeyValuePair = (key: K, value: TForm[K]) => void @@ -11,7 +19,7 @@ type FormDataType = object export interface InertiaFormProps { data: TForm isDirty: boolean - errors: Partial> + errors: Partial, string>> hasErrors: boolean processing: boolean progress: Progress | null @@ -23,9 +31,9 @@ export interface InertiaFormProps { setDefaults(field: keyof TForm, value: FormDataConvertible): void setDefaults(fields: Partial): void reset: (...fields: (keyof TForm)[]) => void - clearErrors: (...fields: (keyof TForm)[]) => void - setError(field: keyof TForm, value: string): void - setError(errors: Record): void + clearErrors: (...fields: FormDataKeys[]) => void + setError(field: FormDataKeys, value: string): void + setError(errors: Partial, string>>): void submit: (method: Method, url: string, options?: VisitOptions) => void get: (url: string, options?: VisitOptions) => void patch: (url: string, options?: VisitOptions) => void @@ -52,8 +60,8 @@ export default function useForm( const recentlySuccessfulTimeoutId = useRef(null) const [data, setData] = rememberKey ? useRemember(defaults, `${rememberKey}:data`) : useState(defaults) const [errors, setErrors] = rememberKey - ? useRemember({} as Partial>, `${rememberKey}:errors`) - : useState({} as Partial>) + ? useRemember({} as Partial, string>>, `${rememberKey}:errors`) + : useState({} as Partial, string>>) const [hasErrors, setHasErrors] = useState(false) const [processing, setProcessing] = useState(false) const [progress, setProgress] = useState(null) @@ -125,7 +133,7 @@ export default function useForm( if (isMounted.current) { setProcessing(false) setProgress(null) - setErrors(errors) + setErrors(errors as Partial, string>>) setHasErrors(true) } @@ -214,13 +222,13 @@ export default function useForm( ) } }, - setError(fieldOrFields: keyof TForm | Record, maybeValue?: string) { + setError(fieldOrFields: FormDataKeys | Partial, string>>, maybeValue?: string) { setErrors((errors) => { const newErrors = { ...errors, ...(typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } - : (fieldOrFields as Record)), + : fieldOrFields), } setHasErrors(Object.keys(newErrors).length > 0) return newErrors @@ -228,10 +236,10 @@ export default function useForm( }, clearErrors(...fields) { setErrors((errors) => { - const newErrors = (Object.keys(errors) as Array).reduce( + const newErrors = Object.keys(errors).reduce( (carry, field) => ({ ...carry, - ...(fields.length > 0 && !fields.includes(field) ? { [field]: errors[field] } : {}), + ...(fields.length > 0 && !fields.includes(field as FormDataKeys) ? { [field]: errors[field as FormDataKeys] } : {}), }), {}, ) diff --git a/packages/vue3/src/useForm.ts b/packages/vue3/src/useForm.ts index d0a236b63..2dffc6532 100644 --- a/packages/vue3/src/useForm.ts +++ b/packages/vue3/src/useForm.ts @@ -3,11 +3,19 @@ import cloneDeep from 'lodash.clonedeep' import isEqual from 'lodash.isequal' import { reactive, watch } from 'vue' +type FormDataKeys = { + [K in keyof T & string]: T[K] extends object + ? T[K] extends Array + ? K + : `${K}.${FormDataKeys}` | K + : K; +}[keyof T & string]; + type FormDataType = object interface InertiaFormProps { isDirty: boolean - errors: Partial> + errors: Partial, string>> hasErrors: boolean processing: boolean progress: Progress | null @@ -19,9 +27,9 @@ interface InertiaFormProps { defaults(field: keyof TForm, value: FormDataConvertible): this defaults(fields: Partial): this reset(...fields: (keyof TForm)[]): this - clearErrors(...fields: (keyof TForm)[]): this - setError(field: keyof TForm, value: string): this - setError(errors: Record): this + clearErrors(...fields: FormDataKeys[]): this + setError(field: FormDataKeys, value: string): this + setError(errors: Partial, string>>): this submit(method: Method, url: string, options?: Partial): void get(url: string, options?: Partial): void post(url: string, options?: Partial): void @@ -45,7 +53,7 @@ export default function useForm( const rememberKey = typeof rememberKeyOrData === 'string' ? rememberKeyOrData : null const data = typeof rememberKeyOrData === 'string' ? maybeData : rememberKeyOrData const restored = rememberKey - ? (router.restore(rememberKey) as { data: TForm; errors: Record }) + ? (router.restore(rememberKey) as { data: TForm; errors: Partial, string>> }) : null let defaults = typeof data === 'object' ? cloneDeep(data) : cloneDeep(data()) let cancelToken = null @@ -106,18 +114,18 @@ export default function useForm( return this }, - setError(fieldOrFields: keyof TForm | Record, maybeValue?: string) { + setError(fieldOrFields: FormDataKeys | Partial, string>>, maybeValue?: string) { Object.assign(this.errors, typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields) this.hasErrors = Object.keys(this.errors).length > 0 return this }, - clearErrors(...fields) { - this.errors = Object.keys(this.errors).reduce( + clearErrors(...fields: FormDataKeys[]) { + this.errors = Object.keys(this.errors as object).reduce( (carry, field) => ({ ...carry, - ...(fields.length > 0 && !fields.includes(field) ? { [field]: this.errors[field] } : {}), + ...(fields.length > 0 && !fields.includes(field as FormDataKeys) ? { [field]: this.errors[field as FormDataKeys] } : {}), }), {}, ) @@ -176,7 +184,7 @@ export default function useForm( onError: (errors) => { this.processing = false this.progress = null - this.clearErrors().setError(errors) + this.clearErrors().setError(errors as Partial, string>>) if (options.onError) { return options.onError(errors)