Back to comparisons

Settings Page

Profile and notification settings forms. AI output missed dirty state tracking, form reset after save, success toasts, and optimistic updates on toggles. The production version fixes all issues found in review.

AI Generated
1export function ProfileForm() {
2 const dispatch = useAppDispatch();
3 const { settings, saveStatus, error } = useAppSelector(
4 (state) => state.settings
5 );
6
7 const {
8 register,
9 handleSubmit,
10 formState: { errors },
11 } = useForm<ProfileValues>({
12 resolver: zodResolver(profileSchema),
13 mode: "onBlur",
14 defaultValues: {
15 name: settings?.profile.name ?? "",
16 bio: settings?.profile.bio ?? "",
17 },
18 });
19
20 useEffect(() => {
21 dispatch(clearSettingsError());
22 }, [dispatch]);
23
24 function onSubmit(data: ProfileValues) {
25 dispatch(updateProfile({ name: data.name, bio: data.bio ?? "" }));
26 }
27
28 const isSaving = saveStatus === "saving";
29
30 return (
31 <form onSubmit={handleSubmit(onSubmit)} className="grid gap-6" noValidate>
32 {error && saveStatus === "failed" && (
33 <div role="alert" className="text-sm text-destructive">
34 {error}
35 </div>
36 )}
37
38 <div className="grid gap-2">
39 <Label htmlFor="name">Name</Label>
40 <Input
41 id="name"
42 placeholder="Your name"
43 disabled={isSaving}
44 aria-invalid={!!errors.name}
45 {...register("name")}
46 />
47 {errors.name && (
48 <p className="text-sm text-destructive">
49 {errors.name.message}
50 </p>
51 )}
52 </div>
53
54 <div className="grid gap-2">
55 <Label htmlFor="bio">Bio</Label>
56 <textarea
57 id="bio"
58 placeholder="Tell us a little about yourself"
59 disabled={isSaving}
60 aria-invalid={!!errors.bio}
61 {...register("bio")}
62 />
63 {errors.bio && (
64 <p className="text-sm text-destructive">
65 {errors.bio.message}
66 </p>
67 )}
68 </div>
69
70 <div>
71 <Button type="submit" disabled={isSaving}>
72 {isSaving ? "Saving..." : "Save changes"}
73 </Button>
74 </div>
75 </form>
76 );
77}
fixNo isDirty tracking from formStateL7–11

The AI destructured formState but only pulled out errors. Without isDirty, the save button is always enabled even when the user has made no changes.

fixSave button always enabledL73–75

The button is disabled only when isSaving is true. It should also be disabled when the form is clean (!isDirty). Users can spam save with unchanged data.

fixNo form reset after successful saveL22–28

After a successful save, the form's default values are not updated. isDirty stays true because react-hook-form still thinks the current values differ from the original defaults. form.reset() with new values is needed.

fixisDirty and reset extracted from formStateL9–11

Both isDirty and reset are destructured so the component can track dirty state and reset the form baseline after a successful save.

fixForm reset + toast on save successL23–34

A useEffect watches for saveStatus === "succeeded", then calls reset() with the new settings values. This clears isDirty and shows a sonner toast. resetSaveStatus() prevents the effect from re-triggering.

fixSave button disabled when form is cleanL93

disabled={!isDirty || isSaving} ensures the button is only clickable when the user has actually changed something.