react-hook-form not working when onsubmit

385 Views Asked by At

Upon clicking the SubmitBtn in the LoginForm component for the first time, the form fails to submit, and nothing appears to happen. The onInvalid function returns an empty object ({}). However, on the second attempt, the onInvalid function provides the following feedback:

{
    username: { message: 'Required', type: 'invalid_type', ref: undefined },
    confirmPassword: { message: 'Required', type: 'invalid_type', ref: undefined }
}

Despite this detailed validation error, the form remains unsubmitted, and no observable changes occur.

LogInForm :

import { FormControl } from '@chakra-ui/react'
import SubmitBtn from '../SubmitBtn'
import InputComponent from '../InputComponent';
import LoginService from '../../../services/loginService';

const LoginForm = () => {
    const { handleSubmit, onsubmit, register, isPending, errors, onInvalid } = LoginService()
    return (
        <form onSubmit={handleSubmit(onsubmit, onInvalid)}>
            <FormControl isRequired mb='4'>
                <InputComponent label='Email Address' register={register} errorsType={errors} errorsMessage={errors.email?.message} placeholder='[email protected]' type='email' name='email' helper='please make sure to enter a valid email address' />
            </FormControl>
            <FormControl isRequired mb='4'>
                <InputComponent label='Password' register={register} errorsType={errors} errorsMessage={errors.password?.message} placeholder='enter your password: ******' type='password' name='password' helper='please make sure to enter the correct password' />
            </FormControl>
            <SubmitBtn loading={isPending} textloading='logging in' >Login</SubmitBtn>
        </form>
    )
}

export default LoginForm

InputComponent

import { FormHelperText, Input, FormLabel } from '@chakra-ui/react'
import type { FieldErrors, UseFormRegister } from 'react-hook-form';
import { FormType } from '../../types/types';

type InputComponentPropsTypes = {
    register: UseFormRegister<FormType>,
    errorsType?: FieldErrors<FormType> | undefined,
    errorsMessage: string | undefined,
    type: React.HTMLInputTypeAttribute,
    name: keyof FormType
    label: string
    placeholder: string,
    helper: string
}

const InputComponent = ({ label, errorsType, errorsMessage, register, name, type, placeholder, helper }: InputComponentPropsTypes) => {
    return (
        <>
            <FormLabel>{label}</FormLabel>
            <Input {...register(name)} type={type} placeholder={placeholder} focusBorderColor='teal.500' />
            {errorsType && errorsType[name] ?
                <FormHelperText color='red'>{errorsMessage}</FormHelperText>
                :
                <FormHelperText>{helper}</FormHelperText>
            }
        </>
    )
}

export default InputComponent

LoginService

import { useToast } from '@chakra-ui/react'
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'
import axios from 'axios'
import { useMutation } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { z } from 'zod'
import { useStore } from '../utils/store';
import { FormSchema, FormType } from '../types/types';
import { API_URL } from '../constants';

const LoginService = () => {
    const setToken = useStore(store => store.setToken)
    const toast = useToast()
    const navigation = useNavigate()
    const { reset, register, handleSubmit, formState: { errors } } = useForm<FormType>({ resolver: zodResolver(FormSchema) })
    const { mutateAsync: loginUser, isPending } = useMutation({
        mutationKey: ['loginUser'],
        mutationFn: async (user: FormType) => {
            await axios.post(`${API_URL}auth/login`, user).then(res => {
                setToken(res.data.token)
                localStorage.setItem('token', res.data.token)
            })
            reset()
        }
    })
    const onInvalid = () => console.log(errors)

    const onsubmit = (user: FormType) => {
        console.log('clicked')
        loginUser(user, {
            onSuccess: () => {
                toast({
                    title: 'successfully logged in',
                    position: 'top',
                    status: 'success',
                    isClosable: true,
                })
                navigation('/home')
            },
            onError: (error) => {
                if (error instanceof z.ZodError) {
                    toast({
                        title: `${error.issues[0].message}`,
                        position: 'top',
                        status: 'error',
                        isClosable: true,
                    })
                } else toast({
                    title: 'something went wrong',
                    position: 'top',
                    status: 'error',
                    isClosable: true,
                })
            }
        })
    }

    return {
        onsubmit,
        onInvalid,
        register,
        handleSubmit,
        isPending,
        errors
    }
}

export default LoginService

Types

import { z } from "zod"

export type AuthType = 'register' | 'login'

export const FormSchema = z.object({
    username: z.string().nullable(),
    email: z.string().email(),
    password: z.string().min(8, { message: 'Must be 8 or more characters long' }),
    confirmPassword: z.string().nullable()
}).refine(data => data.password === data.confirmPassword, {
    message: 'confirmed password must match the password',
    path: ['confirmPassword']
})

export type FormType = z.infer<typeof FormSchema>;

The form was functioning correctly initially. However, after splitting it into multiple components, the issue arose.

2

There are 2 best solutions below

0
On BEST ANSWER

The solution I found is to add a new schema for the login.

export type AuthType = 'register' | 'login'

export const RegisterFormSchema = z.object({
    username: z.string().optional(),
    email: z.string().email(),
    password: z.string().min(8, { message: 'Must be 8 or more characters long' }),
    confirmPassword: z.string().optional()
}).refine(data => data.password === data.confirmPassword, {
    message: 'confirmed password must match the password',
    path: ['confirmPassword']
})

export const LoginFormSchema = z.object({
    email: z.string().email(),
    password: z.string().min(8, { message: 'Must be 8 or more characters long' }),
})

export type FormType = z.infer<typeof RegisterFormSchema>;

And picking only the email and password from FormType in loginService.

import { useToast } from '@chakra-ui/react'
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'
import axios from 'axios'
import { useMutation } from '@tanstack/react-query'
import { useNavigate } from 'react-router-dom'
import { z } from 'zod'
import { useStore } from '../utils/store';
import { FormType, LoginFormSchema, RegisterFormSchema } from '../types/types';
import { API_URL } from '../constants';

type logInFormType = Pick<FormType, "email" | "password">

export const LoginService = () => {
    const setToken = useStore(store => store.setToken)
    const toast = useToast()
    const navigation = useNavigate()
    const { register, handleSubmit, formState: { errors }, reset } = useForm<logInFormType>({ resolver: zodResolver(LoginFormSchema) })
    const { mutateAsync: loginUser, isPending } = useMutation({
        mutationKey: ['loginUser'],
        mutationFn: async (user: logInFormType) => {
            await axios.post(`${API_URL}auth/login`, user).then(res => {
                setToken(res.data.token)
                localStorage.setItem('token', res.data.token)
            })
            reset()
        }
    })

    const onsubmit = (user: logInFormType) => {
        loginUser(user, {
            onSuccess: () => {
                toast({
                    title: 'successfully logged in',
                    position: 'top',
                    status: 'success',
                    isClosable: true,
                })
                navigation('/home')
            },
            onError: (error) => {
                if (error instanceof z.ZodError) {
                    toast({
                        title: `${error.issues[0].message}`,
                        position: 'top',
                        status: 'error',
                        isClosable: true,
                    })
                } else toast({
                    title: 'something went wrong',
                    position: 'top',
                    status: 'error',
                    isClosable: true,
                })
            }
        })
    }

    return {
        onsubmit,
        register,
        handleSubmit,
        isPending,
        errors
    }
}

It should work with the optional() method, but I don't know why it didn't work.

1
On

onSubmit function: You are using onSubmit incorrectly in your LoginForm component. You need to pass a single function to the onSubmit attribute of the element, but you're passing two functions (handleSubmit and onsubmit combined).

Form Submission: Ensure that your form submission is properly handled. Check if the handleSubmit function is wrapping your onsubmit function correctly to manage form submission and validation.

To rectify these issues, you can make the following adjustments:

In your LoginForm component, modify the onSubmit attribute of the element:

    enter code hereconst LoginForm = () => {
    const { handleSubmit, onsubmit, register, isPending, errors, onInvalid } = LoginService();

    const onSubmitHandler = (data) => {
        handleSubmit(onsubmit)(data);
    };

    return (
        <form onSubmit={onSubmitHandler}>
            {/* ... rest of your form components */}
        </form>
    );
};

And in your LoginService, make sure to return the handleSubmit function and the necessary handlers:

    // Inside LoginService
return {
    onsubmit,
    onInvalid,
    register,
    handleSubmit, // Return handleSubmit directly
    isPending,
    errors
};

This way, the onSubmitHandler will correctly invoke both the handleSubmit and onsubmit functions in sequence when the form is submitted.