refactor: scroll to first error when submitting a form

This commit is contained in:
Nicolas Meienberger 2026-02-18 19:33:58 +01:00
parent 505f60a8a1
commit d34bd53275
5 changed files with 35 additions and 4 deletions

View file

@ -0,0 +1,10 @@
import { useCallback } from "react";
export function useScrollToFormError() {
return useCallback(() => {
setTimeout(() => {
const firstError = document.querySelector("[data-slot='form-message']");
firstError?.scrollIntoView({ behavior: "smooth", block: "center" });
}, 50);
}, []);
}

View file

@ -1,6 +1,7 @@
import { arktypeResolver } from "@hookform/resolvers/arktype";
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import { Form } from "~/client/components/ui/form";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/client/components/ui/card";
import type { BackupSchedule, Volume } from "~/client/lib/types";
@ -30,6 +31,8 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
defaultValues: backupScheduleToFormValues(initialValues),
});
const scrollToFirstError = useScrollToFormError();
const handleSubmit = useCallback(
(data: InternalFormValues) => {
const {
@ -100,7 +103,7 @@ export const CreateScheduleForm = ({ initialValues, formId, onSubmit, volume }:
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
onSubmit={form.handleSubmit(handleSubmit, scrollToFirstError)}
className="grid gap-4 xl:grid-cols-[minmax(0,2.3fr)_minmax(320px,1fr)]"
id={formId}
>

View file

@ -15,6 +15,7 @@ import {
import { Input } from "~/client/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/client/components/ui/select";
import { notificationConfigSchemaBase } from "~/schemas/notifications";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import {
CustomForm,
DiscordForm,
@ -116,11 +117,16 @@ export const CreateNotificationForm = ({ onSubmit, mode = "create", initialValue
});
const { watch } = form;
const scrollToFirstError = useScrollToFormError();
const watchedType = watch("type");
return (
<Form {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
<form
id={formId}
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
className={cn("space-y-4", className)}
>
<FormField
control={form.control}
name="name"

View file

@ -20,6 +20,7 @@ import { SecretInput } from "../../../components/ui/secret-input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../components/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
import { useSystemInfo } from "~/client/hooks/use-system-info";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import { COMPRESSION_MODES, repositoryConfigSchemaBase } from "~/schemas/restic";
import { Checkbox } from "../../../components/ui/checkbox";
import {
@ -96,10 +97,15 @@ export const CreateRepositoryForm = ({
const [passwordMode, setPasswordMode] = useState<"default" | "custom">("default");
const { capabilities } = useSystemInfo();
const scrollToFirstError = useScrollToFormError();
return (
<Form {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
<form
id={formId}
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
className={cn("space-y-4", className)}
>
<FormField
control={form.control}
name="name"

View file

@ -22,6 +22,7 @@ import { volumeConfigSchemaBase } from "~/schemas/volumes";
import { testConnectionMutation } from "../../../api-client/@tanstack/react-query.gen";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip";
import { useSystemInfo } from "~/client/hooks/use-system-info";
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
export const formSchema = type({
@ -65,6 +66,7 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
const { getValues, watch } = form;
const { capabilities } = useSystemInfo();
const scrollToFirstError = useScrollToFormError();
const watchedBackend = watch("backend");
const [testMessage, setTestMessage] = useState<{ success: boolean; message: string } | null>(null);
@ -102,7 +104,11 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
return (
<Form {...form}>
<form id={formId} onSubmit={form.handleSubmit(onSubmit)} className={cn("space-y-4", className)}>
<form
id={formId}
onSubmit={form.handleSubmit(onSubmit, scrollToFirstError)}
className={cn("space-y-4", className)}
>
<FormField
control={form.control}
name="name"