最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - React Hook Form: 'onChange' Mode Still Triggers Validation on Blur and Affects Submit - Stack Overflow

programmeradmin0浏览0评论

I have a form where new users can create a username.

To check if the username is already taken, I use a validation method that verifies its availability. I set the mode of React Hook Form to 'onChange' so that users can see the availability status in real time.

However, I noticed that the form still revalidates on blur. Additionally, on the first submit attempt, it seems to trigger only the onBlur event instead of submitting the form. However, clicking submit again works as expected.

const createDebouncedValidator = () => {
  let currentPromise: Promise<boolean> | null = null
  const debouncedCheck = debounce((username: string, resolve: (value: boolean) => void) => {
    checkAvailability('name', username)
      .then(({ available }) => resolve(available))
      .catch(() => resolve(false))
  }, 500)

  return async (username: string) => {
    if (username.length < 3) return true

    currentPromise = new Promise<boolean>(resolve => {
      debouncedCheck(username, resolve)
    })
    const result = await currentPromise
    currentPromise = null
    return result
  }
}

const onboardingFormSchema = z.object({
  username: z
    .string()
    .min(3, {
      message: 'Username must be at least 3 characters.',
    })
    .max(30, {
      message: 'Username must not be longer than 30 characters.',
    })
    .regex(/^[a-z0-9-]+$/, {
      message: 'Username can only contain lowercase letters, numbers, and hyphens.',
    })
    .refine(name => !name.startsWith('-') && !name.endsWith('-'), {
      message: 'Username cannot start or end with a hyphen.',
    })
    .refine(createDebouncedValidator(), { 
      message: 'This username is already taken' 
    })
})

type OnboardingFormValues = z.infer<typeof onboardingFormSchema>

export function OnboardingForm() {
  const { t } = useTranslations()
  const router = useRouter()
  const { setUser } = useAuth()
  const { finishOnboarding } = useOnboarding()

  const form = useForm<OnboardingFormValues>({
    resolver: zodResolver(onboardingFormSchema),
    defaultValues: {
      username: '',
    },
    mode: 'onChange',
  })

  const watchUsername = form.watch('username')

  async function onSubmit(data: OnboardingFormValues) {
    try {
      // Update username
      const updatedUser = await updateUsername(data.username)
      
      // Update global user state
      setUser(updatedUser)
      
      toast({
        title: t('settings.profile.updated'),
        description: t('settings.profile.usernameUpdated')
      })

      // Finish onboarding and redirect
      finishOnboarding()
      router.push('/links')
    } catch (error) {
      console.error('Profile update error:', error)
      toast({
        variant: 'destructive',
        title: t('common.error'),
        description: t('settings.profile.updateError')
      })
    }
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className='space-y-8'>
        <FormField
          control={form.control}
          name='username'
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <div className="relative">
                  <Input 
                    {...field}
                    placeholder='username'
                    autoComplete='off'
                    autoCapitalize='off'
                  />
                  {form.formState.isValidating && (
                    <div className="absolute right-3 top-1/2 -translate-y-1/2">
                      <IconLoader2 className="h-4 w-4 animate-spin text-muted-foreground" />
                    </div>
                  )}
                </div>
              </FormControl>
              <FormDescription className="space-y-2">
                <p className="font-mono">
                  palmlink.eu/
                  <span className={cn(
                    watchUsername ? 'text-foreground' : 'text-muted-foreground',
                    'transition-colors'
                  )}>
                    {watchUsername || 'username'}
                  </span>
                </p>
                <ul className="text-xs list-disc list-inside space-y-1">
                  <li>Must be between 3 and 30 characters</li>
                  <li>Can only contain lowercase letters, numbers, and hyphens</li>
                  <li>Cannot start or end with a hyphen</li>
                </ul>
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button 
          type='submit' 
          className='w-full'
          disabled={!form.formState.isValid || form.formState.isValidating || form.formState.isSubmitting}
        >
          {form.formState.isSubmitting ? t('common.saving') : 'Continue'}
        </Button>
      </form>
    </Form>
  )
} 
发布评论

评论列表(0)

  1. 暂无评论