shanG
shanG

Reputation: 61

React hooks form onChange misbehave after error validation occured

In my simple crud application, when I try to add a new author with an invalid name format and try to submit form display an error and after that when I have press backspace twice to erase last letter in textbox.

Here is my AuthorForm.tsx

import React, {useEffect, useState} from 'react';
import {Row, Col, Form, Button} from 'react-bootstrap';
import {IAuthor} from "../../assets/types/LibraryTypes";
import {XCircle} from "react-feather";
import {useForm} from "react-hook-form";

interface IFormInputs {
    authorName: string
}

type CreateFormProps = {
    onClose: () => void,
    onAuthorAdded: (author:IAuthor)=>void,
    onAuthorToUpdate: IAuthor | null,
    updateAuthorIndex : number | null,
    onAuthorUpdated : (updatedAuthor:IAuthor, index:number)=>void,
}

const AuthorForm:React.FC<CreateFormProps> = (props) =>{

    const [authorName, setAuthorName] = useState<string|null>(null);
    const { register, errors, handleSubmit } = useForm<IFormInputs>({mode:'onTouched'});

    useEffect(()=>{
        if(!props.onAuthorToUpdate){
            setAuthorName(null);
            return;
        }
        setAuthorName(props.onAuthorToUpdate.name);
    },[props.onAuthorToUpdate]);

    const handleOnNameChange = (event:React.ChangeEvent<HTMLInputElement>)=>{
        event.preventDefault();
        setAuthorName(event.target.value);
    }
    const handleOnCreate = () =>{
        if(!authorName){
            return;
        }

        if(props.onAuthorToUpdate && props.updateAuthorIndex !== null){
            props.onAuthorUpdated({...props.onAuthorToUpdate,name:authorName},props.updateAuthorIndex);
            setAuthorName(null);
            return;
        }

        const newAuthor: IAuthor = {name:authorName};
        props.onAuthorAdded(newAuthor);
        setAuthorName(null);
    };


    return(
        <Col className='p-0' sm={10}>
            <Row className=' pb-1 mb-3 mx-1'>
                <Col xs={10}>
                    <span className='add-book-title pt-2'>
                        {!props.onAuthorToUpdate && 'Create Author'}
                        {props.onAuthorToUpdate && 'Update Author'}
                    </span>
                </Col>
                <Col className='closeBtn text-right p-0' xs={2}>
                    <XCircle color='#363636' className='mt-2 mr-3' onClick={props.onClose}/>
                </Col>
            </Row>
            <Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
                <Form.Group>
                    <Form.Row>
                        <Form.Label column="sm" xs={6} className='label'>
                            Name of the Author
                        </Form.Label>
                        <Col xs={6} className='warning text-right mt-2 pr-2'>
                            {errors.authorName?.type === "required" && (
                                <p>This field is required</p>
                            )}
                            {errors.authorName?.type === "maxLength" && (
                                <p>Author Name name cannot exceed 50 characters</p>
                            )}
                            {errors.authorName?.type === "pattern" && (
                                <p>Invalid Author Name</p>
                            )}
                        </Col>
                        <Col sm={12}>
                            <Form.Control size={"sm"}
                                          name="authorName"
                                          ref={register({
                                              required: true,
                                              maxLength: 50,
                                              pattern: /^[a-zA-Z\s]+$/
                                          })}
                                          onChange={
                                              (event:React.ChangeEvent<HTMLInputElement>)=>
                                                  handleOnNameChange(event)
                                          }
                                          value={authorName?authorName:''}

                            />

                        </Col>
                    </Form.Row>
                </Form.Group>
                    <Col className='text-right mb-3 p-0' xs={12}>
                        <Button type={"submit"} variant={"primary"} size={"sm"} className={"px-3 pt-1"}>
                            {!props.onAuthorToUpdate && 'Create'}
                            {props.onAuthorToUpdate && 'Update'}
                        </Button>
                    </Col>
            </Form>
        </Col>
    )
};

export default AuthorForm;

And this is AuthorList.tsx

import React, {useEffect, useState} from 'react';
import {Container} from 'react-bootstrap';
import AuthorAddedList from "./AuthorAddedList";
import AuthorForm from "./AuthorForm";
import AuthorWelcome from "./AuthorWelcome";
import CreateAuthor from "./CreateAuthor";
import {IAuthor} from "../../assets/types/LibraryTypes";


const AuthorList:React.FC = () =>{

        const initAuthors: IAuthor[] = [];
        const [authors, setAuthors] = useState<IAuthor[]>(initAuthors);
        const [isFormVisible, setIsFormVisible] = useState<boolean>(false);
        const [authorToUpdate, setAuthorToUpdate] = useState<IAuthor | null>(null);
        const [updateAuthorIndex, setUpdateAuthorIndex] = useState<number| null>(null)

        useEffect(()=>{
            if(!authorToUpdate){
                return;
            }
            setIsFormVisible(true);
        },[authorToUpdate]);

        const handleOnCreateClick = () => {
            setIsFormVisible(true);
            setAuthorToUpdate(null);
        };


        const handleOnFormClosed = () => {
            setIsFormVisible(false);
        }

        const handleAuthorAdded = (newAuthor: IAuthor) => {
            const allAuthors: IAuthor[] = authors.slice();
            allAuthors.push(newAuthor)
            setAuthors(allAuthors);
        };

        const handleAuthorDeleted = (index: number) => {
            const allAuthors: IAuthor[] = authors.slice();
            allAuthors.splice(index, 1);
            setAuthors(allAuthors);
        }

        const handleOnUpdateRequest = (index: number) => {
            setAuthorToUpdate(authors[index]);
            setUpdateAuthorIndex(index);
            setIsFormVisible(true);
        }

        const handleOnAuthorUpdated = (updatedAuthor: IAuthor, index:number) =>{
            const allAuthors : IAuthor [] = authors.slice();
            allAuthors.splice(index,1, updatedAuthor);
            setAuthors(allAuthors)
        }

        return (

            <Container fluid={true} className={"authors"}>
                <AuthorWelcome/>
                <AuthorAddedList authors={authors} onDeleted={handleAuthorDeleted} onUpdateRequested={handleOnUpdateRequest} />
                <CreateAuthor onClickCreate={handleOnCreateClick}/>
                {isFormVisible &&
                <AuthorForm onClose={handleOnFormClosed} onAuthorAdded={handleAuthorAdded} onAuthorToUpdate={authorToUpdate} onAuthorUpdated={handleOnAuthorUpdated} updateAuthorIndex={updateAuthorIndex}/>}
            </Container>
        )
    }

export default AuthorList;

Here is the sandbox link to my full code Click Here

to demonstrate the error,

  1. go to the sandbox link
  2. click add author button in webapp
  3. enter an invalid name like 'john97'
  4. then submit the form
  5. then try to clear the name using backspace.
  6. now you can see to erase last letter 'j' you have to press backspace twice

please help me to solve this issue

thank you

Upvotes: 1

Views: 2846

Answers (2)

mj.
mj.

Reputation: 163

I think using Controller component of react-hook-form is better at handling controlled components, you also don't have to set onChange event and make your code much cleaner.

Using this in AuthorForm.tsx seems to make your weird bug fixed.

type FormData = {
  authorName: string;
}

//some codes...
const { register, handleSubmit, control, errors, setValue, reset } = useForm<FormData>();

//some codes...
const handleOnCreate = (data: FormData) => {
    if (!data?.authorName) {
      return;
    }

    if (props.onAuthorToUpdate && props.updateAuthorIndex !== null) {
      props.onAuthorUpdated(
        { ...props.onAuthorToUpdate, name: data.authorName },
        props.updateAuthorIndex
      );
      reset({ authorName: "" }); // or setValue("authorName", "");
      return;
    }

    const newAuthor: IAuthor = { name: data.authorName };
    props.onAuthorAdded(newAuthor);
    reset({ authorName: "" }); // or setValue("authorName", "");
  };


//some codes...
<Controller
   control={control}
   name={"authorName"}
   as={<Form.Control size={"sm"} />}
   defaultValue=""
   rules={{
     required: true,
     maxLength: 50,
     pattern: /^[A-Za-z ]+$/i
   }}
/>

Here is the sandbox.

Upvotes: 1

sh.alawneh
sh.alawneh

Reputation: 649

the problem is in the AuthorForm.tsx file, in this line:

<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>

the onSubmit shouldn't accept an invoked function handleSubmit(handleOnCreate), it should be changed to onSubmit={() => handleSubmit(handleOnCreate)}

Upvotes: 0

Related Questions