Skip to content

Commit 3057d0a

Browse files
authored
[2.x] Fix useForm type inference when passing data as callback (#2878)
* Fix `useForm` type inference with callbacks and generics * fix * fix
1 parent b751f51 commit 3057d0a

File tree

4 files changed

+122
-26
lines changed

4 files changed

+122
-26
lines changed

packages/svelte/src/useForm.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,25 @@ export type InertiaPrecognitiveForm<TForm extends object> = InertiaForm<TForm> &
125125

126126
type ReservedFormKeys = keyof InertiaFormProps<any>
127127

128-
type ValidateFormData<T> = string extends keyof T
129-
? T
130-
: {
131-
[K in keyof T]: K extends ReservedFormKeys ? ['Error: This field name is reserved by useForm:', K] : T[K]
132-
}
128+
type ValidateFormData<T> = {
129+
[K in keyof T]: K extends ReservedFormKeys ? ['Error: This field name is reserved by useForm:', K] : T[K]
130+
}
133131

134-
export default function useForm<TForm extends FormDataType<TForm>>(
132+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
135133
method: Method | (() => Method),
136134
url: string | (() => string),
137-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
135+
data: TForm | (() => TForm),
138136
): InertiaPrecognitiveFormStore<TForm>
139-
export default function useForm<TForm extends FormDataType<TForm>>(
137+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
140138
urlMethodPair: UrlMethodPair | (() => UrlMethodPair),
141-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
139+
data: TForm | (() => TForm),
142140
): InertiaPrecognitiveFormStore<TForm>
143-
export default function useForm<TForm extends FormDataType<TForm>>(
141+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
144142
rememberKey: string,
145-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
143+
data: TForm | (() => TForm),
146144
): InertiaFormStore<TForm>
147-
export default function useForm<TForm extends FormDataType<TForm>>(
148-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
145+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
146+
data: TForm | (() => TForm),
149147
): InertiaFormStore<TForm>
150148
export default function useForm<TForm extends FormDataType<TForm>>(): InertiaFormStore<TForm>
151149
export default function useForm<TForm extends FormDataType<TForm>>(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts">
2+
// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it.
3+
import { useForm } from '@inertiajs/svelte'
4+
5+
type FormData = {
6+
name: string
7+
company: { name: string }
8+
users: { name: string }[]
9+
}
10+
11+
const defaultData = {
12+
name: '',
13+
company: { name: '' },
14+
users: [],
15+
}
16+
17+
const form = useForm<FormData>(() => defaultData)
18+
$form.name = 'John Doe'
19+
$form.company.name = 'Acme Corp'
20+
$form.users = [{ name: 'Jane Doe' }]
21+
// @ts-expect-error - A form has no email field
22+
$form.email = 'john@example.com'
23+
// @ts-expect-error - A company has no street field
24+
$form.company.street = '123 Main St'
25+
// @ts-expect-error - A company has no street field
26+
$form.company = { name: 'Acme Corp', street: '123 Main St' }
27+
// @ts-expect-error - A form has no email field
28+
$form.users = [{ name: 'Jane Doe', email: 'jane@example.com' }]
29+
30+
const inferredData = {
31+
name: '',
32+
company: { name: '' },
33+
users: [{ name: '' }],
34+
}
35+
36+
const inferred = useForm(() => inferredData)
37+
$inferred.name = 'John Doe'
38+
$inferred.company.name = 'Acme Corp'
39+
$inferred.users = [{ name: 'Jane Doe' }]
40+
// @ts-expect-error - A form has no email field
41+
$inferred.email = 'john@example.com'
42+
43+
const withRememberKey = useForm<FormData>('myKey', () => defaultData)
44+
$withRememberKey.name = 'John Doe'
45+
// @ts-expect-error - A form has no email field
46+
$withRememberKey.email = 'john@example.com'
47+
48+
// @ts-expect-error - progress is a reserved form key
49+
useForm(() => ({ progress: 1 }))
50+
</script>

packages/vue3/src/useForm.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -127,27 +127,25 @@ export type InertiaPrecognitiveForm<TForm extends object> = InertiaForm<TForm> &
127127

128128
type ReservedFormKeys = keyof InertiaFormProps<any>
129129

130-
type ValidateFormData<T> = string extends keyof T
131-
? T
132-
: {
133-
[K in keyof T]: K extends ReservedFormKeys ? ['Error: This field name is reserved by useForm:', K] : T[K]
134-
}
130+
type ValidateFormData<T> = {
131+
[K in keyof T]: K extends ReservedFormKeys ? ['Error: This field name is reserved by useForm:', K] : T[K]
132+
}
135133

136-
export default function useForm<TForm extends FormDataType<TForm>>(
134+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
137135
method: Method | (() => Method),
138136
url: string | (() => string),
139-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
137+
data: TForm | (() => TForm),
140138
): InertiaPrecognitiveForm<TForm>
141-
export default function useForm<TForm extends FormDataType<TForm>>(
139+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
142140
urlMethodPair: UrlMethodPair | (() => UrlMethodPair),
143-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
141+
data: TForm | (() => TForm),
144142
): InertiaPrecognitiveForm<TForm>
145-
export default function useForm<TForm extends FormDataType<TForm>>(
143+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
146144
rememberKey: string,
147-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
145+
data: TForm | (() => TForm),
148146
): InertiaForm<TForm>
149-
export default function useForm<TForm extends FormDataType<TForm>>(
150-
data: ValidateFormData<TForm> | (() => ValidateFormData<TForm>),
147+
export default function useForm<TForm extends FormDataType<TForm> & ValidateFormData<TForm>>(
148+
data: TForm | (() => TForm),
151149
): InertiaForm<TForm>
152150
export default function useForm<TForm extends FormDataType<TForm>>(): InertiaForm<TForm>
153151
export default function useForm<TForm extends FormDataType<TForm>>(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it.
3+
import { useForm } from '@inertiajs/vue3'
4+
5+
type FormData = {
6+
name: string
7+
company: { name: string }
8+
users: { name: string }[]
9+
}
10+
11+
const defaultData = {
12+
name: '',
13+
company: { name: '' },
14+
users: [],
15+
}
16+
17+
const form = useForm<FormData>(() => defaultData)
18+
form.name = 'John Doe'
19+
form.company.name = 'Acme Corp'
20+
form.users = [{ name: 'Jane Doe' }]
21+
// @ts-expect-error - A form has no email field
22+
form.email = 'john@example.com'
23+
// @ts-expect-error - A company has no street field
24+
form.company.street = '123 Main St'
25+
// @ts-expect-error - A company has no street field
26+
form.company = { name: 'Acme Corp', street: '123 Main St' }
27+
// @ts-expect-error - A form has no email field
28+
form.users = [{ name: 'Jane Doe', email: 'jane@example.com' }]
29+
30+
const inferredData = {
31+
name: '',
32+
company: { name: '' },
33+
users: [{ name: '' }],
34+
}
35+
36+
const inferred = useForm(() => inferredData)
37+
inferred.name = 'John Doe'
38+
inferred.company.name = 'Acme Corp'
39+
inferred.users = [{ name: 'Jane Doe' }]
40+
// @ts-expect-error - A form has no email field
41+
inferred.email = 'john@example.com'
42+
43+
const withRememberKey = useForm<FormData>('myKey', () => defaultData)
44+
withRememberKey.name = 'John Doe'
45+
// @ts-expect-error - A form has no email field
46+
withRememberKey.email = 'john@example.com'
47+
48+
// @ts-expect-error - progress is a reserved form key
49+
useForm(() => ({ progress: 1 }))
50+
</script>

0 commit comments

Comments
 (0)