Skip to content

Commit 322194c

Browse files
Merge pull request #149 from laravel/intertia-form-component
Migrate `useForm` to new Inertia `Form` component
2 parents 740c43f + 02ed025 commit 322194c

File tree

10 files changed

+957
-1577
lines changed

10 files changed

+957
-1577
lines changed

package-lock.json

Lines changed: 596 additions & 1084 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@headlessui/react": "^2.2.0",
27-
"@inertiajs/react": "^2.0.0",
27+
"@inertiajs/react": "^2.1.0",
2828
"@radix-ui/react-avatar": "^1.1.3",
2929
"@radix-ui/react-checkbox": "^1.1.4",
3030
"@radix-ui/react-collapsible": "^1.1.3",

resources/js/components/delete-user.tsx

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
1-
import { useForm } from '@inertiajs/react';
2-
import { FormEventHandler, useRef } from 'react';
3-
1+
import HeadingSmall from '@/components/heading-small';
42
import InputError from '@/components/input-error';
53
import { Button } from '@/components/ui/button';
4+
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
65
import { Input } from '@/components/ui/input';
76
import { Label } from '@/components/ui/label';
8-
9-
import HeadingSmall from '@/components/heading-small';
10-
11-
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
7+
import { Form } from '@inertiajs/react';
8+
import { useRef } from 'react';
129

1310
export default function DeleteUser() {
1411
const passwordInput = useRef<HTMLInputElement>(null);
15-
const { data, setData, delete: destroy, processing, reset, errors, clearErrors } = useForm<Required<{ password: string }>>({ password: '' });
16-
17-
const deleteUser: FormEventHandler = (e) => {
18-
e.preventDefault();
19-
20-
destroy(route('profile.destroy'), {
21-
preserveScroll: true,
22-
onSuccess: () => closeModal(),
23-
onError: () => passwordInput.current?.focus(),
24-
onFinish: () => reset(),
25-
});
26-
};
27-
28-
const closeModal = () => {
29-
clearErrors();
30-
reset();
31-
};
3212

3313
return (
3414
<div className="space-y-6">
@@ -49,38 +29,50 @@ export default function DeleteUser() {
4929
Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password
5030
to confirm you would like to permanently delete your account.
5131
</DialogDescription>
52-
<form method="POST" className="space-y-6" onSubmit={deleteUser}>
53-
<div className="grid gap-2">
54-
<Label htmlFor="password" className="sr-only">
55-
Password
56-
</Label>
5732

58-
<Input
59-
id="password"
60-
type="password"
61-
name="password"
62-
ref={passwordInput}
63-
value={data.password}
64-
onChange={(e) => setData('password', e.target.value)}
65-
placeholder="Password"
66-
autoComplete="current-password"
67-
/>
33+
<Form
34+
method="delete"
35+
action={route('profile.destroy')}
36+
options={{
37+
preserveScroll: true,
38+
}}
39+
onError={() => passwordInput.current?.focus()}
40+
onSubmitComplete={(form) => form.reset()}
41+
className="space-y-6"
42+
>
43+
{({ resetAndClearErrors, processing, errors }) => (
44+
<>
45+
<div className="grid gap-2">
46+
<Label htmlFor="password" className="sr-only">
47+
Password
48+
</Label>
49+
50+
<Input
51+
id="password"
52+
type="password"
53+
name="password"
54+
ref={passwordInput}
55+
placeholder="Password"
56+
autoComplete="current-password"
57+
/>
6858

69-
<InputError message={errors.password} />
70-
</div>
59+
<InputError message={errors.password} />
60+
</div>
7161

72-
<DialogFooter className="gap-2">
73-
<DialogClose asChild>
74-
<Button variant="secondary" onClick={closeModal}>
75-
Cancel
76-
</Button>
77-
</DialogClose>
62+
<DialogFooter className="gap-2">
63+
<DialogClose asChild>
64+
<Button variant="secondary" onClick={() => resetAndClearErrors()}>
65+
Cancel
66+
</Button>
67+
</DialogClose>
7868

79-
<Button variant="destructive" disabled={processing} asChild>
80-
<button type="submit">Delete account</button>
81-
</Button>
82-
</DialogFooter>
83-
</form>
69+
<Button variant="destructive" disabled={processing} asChild>
70+
<button type="submit">Delete account</button>
71+
</Button>
72+
</DialogFooter>
73+
</>
74+
)}
75+
</Form>
8476
</DialogContent>
8577
</Dialog>
8678
</div>
Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,38 @@
1-
// Components
2-
import { Head, useForm } from '@inertiajs/react';
3-
import { LoaderCircle } from 'lucide-react';
4-
import { FormEventHandler } from 'react';
5-
61
import InputError from '@/components/input-error';
72
import { Button } from '@/components/ui/button';
83
import { Input } from '@/components/ui/input';
94
import { Label } from '@/components/ui/label';
105
import AuthLayout from '@/layouts/auth-layout';
6+
import { Form, Head } from '@inertiajs/react';
7+
import { LoaderCircle } from 'lucide-react';
118

129
export default function ConfirmPassword() {
13-
const { data, setData, post, processing, errors, reset } = useForm<Required<{ password: string }>>({
14-
password: '',
15-
});
16-
17-
const submit: FormEventHandler = (e) => {
18-
e.preventDefault();
19-
20-
post(route('password.confirm'), {
21-
onFinish: () => reset('password'),
22-
});
23-
};
24-
2510
return (
2611
<AuthLayout
2712
title="Confirm your password"
2813
description="This is a secure area of the application. Please confirm your password before continuing."
2914
>
3015
<Head title="Confirm password" />
3116

32-
<form method="POST" onSubmit={submit}>
33-
<div className="space-y-6">
34-
<div className="grid gap-2">
35-
<Label htmlFor="password">Password</Label>
36-
<Input
37-
id="password"
38-
type="password"
39-
name="password"
40-
placeholder="Password"
41-
autoComplete="current-password"
42-
value={data.password}
43-
autoFocus
44-
onChange={(e) => setData('password', e.target.value)}
45-
/>
46-
47-
<InputError message={errors.password} />
48-
</div>
49-
50-
<div className="flex items-center">
51-
<Button className="w-full" disabled={processing}>
52-
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
53-
Confirm password
54-
</Button>
17+
<Form method="post" action={route('password.confirm')} onSubmitComplete={(form) => form.reset('password')}>
18+
{({ processing, errors }) => (
19+
<div className="space-y-6">
20+
<div className="grid gap-2">
21+
<Label htmlFor="password">Password</Label>
22+
<Input id="password" type="password" name="password" placeholder="Password" autoComplete="current-password" autoFocus />
23+
24+
<InputError message={errors.password} />
25+
</div>
26+
27+
<div className="flex items-center">
28+
<Button className="w-full" disabled={processing}>
29+
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
30+
Confirm password
31+
</Button>
32+
</div>
5533
</div>
56-
</div>
57-
</form>
34+
)}
35+
</Form>
5836
</AuthLayout>
5937
);
6038
}

resources/js/pages/auth/forgot-password.tsx

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Components
2-
import { Head, useForm } from '@inertiajs/react';
2+
import { Form, Head } from '@inertiajs/react';
33
import { LoaderCircle } from 'lucide-react';
4-
import { FormEventHandler } from 'react';
54

65
import InputError from '@/components/input-error';
76
import TextLink from '@/components/text-link';
@@ -11,47 +10,32 @@ import { Label } from '@/components/ui/label';
1110
import AuthLayout from '@/layouts/auth-layout';
1211

1312
export default function ForgotPassword({ status }: { status?: string }) {
14-
const { data, setData, post, processing, errors } = useForm<Required<{ email: string }>>({
15-
email: '',
16-
});
17-
18-
const submit: FormEventHandler = (e) => {
19-
e.preventDefault();
20-
21-
post(route('password.email'));
22-
};
23-
2413
return (
2514
<AuthLayout title="Forgot password" description="Enter your email to receive a password reset link">
2615
<Head title="Forgot password" />
2716

2817
{status && <div className="mb-4 text-center text-sm font-medium text-green-600">{status}</div>}
2918

3019
<div className="space-y-6">
31-
<form method="POST" onSubmit={submit}>
32-
<div className="grid gap-2">
33-
<Label htmlFor="email">Email address</Label>
34-
<Input
35-
id="email"
36-
type="email"
37-
name="email"
38-
autoComplete="off"
39-
value={data.email}
40-
autoFocus
41-
onChange={(e) => setData('email', e.target.value)}
42-
placeholder="[email protected]"
43-
/>
44-
45-
<InputError message={errors.email} />
46-
</div>
47-
48-
<div className="my-6 flex items-center justify-start">
49-
<Button className="w-full" disabled={processing}>
50-
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
51-
Email password reset link
52-
</Button>
53-
</div>
54-
</form>
20+
<Form method="post" action={route('password.email')}>
21+
{({ processing, errors }) => (
22+
<>
23+
<div className="grid gap-2">
24+
<Label htmlFor="email">Email address</Label>
25+
<Input id="email" type="email" name="email" autoComplete="off" autoFocus placeholder="[email protected]" />
26+
27+
<InputError message={errors.email} />
28+
</div>
29+
30+
<div className="my-6 flex items-center justify-start">
31+
<Button className="w-full" disabled={processing}>
32+
{processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
33+
Email password reset link
34+
</Button>
35+
</div>
36+
</>
37+
)}
38+
</Form>
5539

5640
<div className="space-x-1 text-center text-sm text-muted-foreground">
5741
<span>Or, return to</span>

0 commit comments

Comments
 (0)