Home > Mobile >  Button takes two clicks using boolean state hook
Button takes two clicks using boolean state hook

Time:11-17

I'm fairly new to React. Here I've made a small form component for a project (with a bit of tailwind included). Above the form proper is a hidden alert box that will show on submission (green for success and red for fail). The handler that I have attached to the form shows the correct alert, however it takes two clicks. In the handler validateFormData() I'm resetting state (isError). I'm aware the useState hook is asynchronous, so my isError variable is not updating properly before I render my alert box (right?). I've tried passing callbacks to my setIsError functions (after looking online for solutions) but I could not resolve the issue. Am I even in the right ball park here? Or am I missing something?



function ContactInsert() {
    const contactForm = useRef(null);
    const alertBox = useRef(null);
    const [isError, setIsError] = useState(false);

    function showAlertBox() {
        alertBox.current.classList.add("alert-show");
        setTimeout(() => {
            alertBox.current.classList.remove("alert-show");
        }, 3000);
    }

    async function validateFormData() {
        // unpack and validate data
        const name = contactForm.current.querySelector("[name=name]").value.trim();
        const email = contactForm.current.querySelector("[name=email]").value.trim();
        const comment = contactForm.current.querySelector("[name=comment]").value.trim();

        if (name.length > 0 && email.length > 0 && comment.length > 0) {
            setIsError(false);
        } else {
            setIsError(true);
        }
        showAlertBox();
    }


    return (
        <div className="flex flex-col">
            <div className="flex justify-center my-2">
                <h1>Drop me a message!</h1>
            </div>
            {isError ?
                <div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] invisible">hello</div> :
                <div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#77ff6e] invisible">hello</div>
            }
            <form 
            className="flex flex-col items-center justify-center md:h-full" 
            method="POST"
            name="contact"
            id="contact"
            type="submit"
            ref={contactForm}
            onSubmit={(e) => {
                e.preventDefault();
                validateFormData();
            }}
            >
                <div className="flex flex-col justify-between w-2/3">
                    <label>name</label>
                    <input type="text" name="name"/>
                </div>
                <br/>
                <div className="flex flex-col justify-between w-2/3">
                    <label>email</label>
                    <input type="text" name="email"/>
                </div>
                <br/>
                <div className="flex flex-col justify-between w-2/3 h-40">
                    <label>comment</label>
                    <textarea className="h-full" name="comment"/>
                </div>
                <div className="flex w-2/3 justify-start my-4">
                    <button className="p-1" form="contact">Submit</button>
                </div>
            </form>
        </div>
    );
}

CodePudding user response:

It is always a bad idea to manipulate DOM directly as React will have a hard time to match the rendered output and defeats the purpose of using react. Better to store this info in the state and let react handle it.

Also setState is asynchronous, and calling that will force the react component to rerender. We can use useEffect to let React know that component has to do something on next render.

function ContactInsert() {
  const [isError, setIsError] = useState(false);
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [comment, setComment] = useState('');
  const [displayAlert, setDisplayAlert] = useState(false); 
  
  const handleNameOnchangeHandler = (event) => {
      setName(event.target.value);
  };
  
  const handleEmailOnchangeHandler = (event) => {
      setName(event.target.value);
  };
  
  const handleCommentOnchangeHandler = (event) => {
      setName(event.target.value);
  };

  const validateFormData = () => {
    const hasError = !name || !email || !comment;
  
    setIsError(hasError);
  }
  
  useEffect(() => {
    if(isError) {
        setDisplayAlert(true);
      
      setTimeout(() => {
        setDisplayAlert(false);
        setIsError(false);
      }, 3000);
    }
  }, [isError]);


  return (
        <div className="flex flex-col">
            <div className="flex justify-center my-2">
                <h1>Drop me a message!</h1>
            </div>
            {displayAlert ?
                <div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] show-alert">hello</div> : null
                
            }
            <form 
              className="flex flex-col items-center justify-center md:h-full" 
              method="POST"
              name="contact"
              id="contact"
              type="submit"
              ref={contactForm}
              onSubmit={(e) => {
                  e.preventDefault();
                  validateFormData();
              }}
            >
                <div className="flex flex-col justify-between w-2/3">
                    <label>name</label>
                    <input type="text" name="name" onchange={handleNameOnchangeHandler}/>
                </div>
                <br/>
                <div className="flex flex-col justify-between w-2/3">
                    <label>email</label>
                    <input type="text" name="email" onchange={handleEmailOnchangeHandler}/>
                </div>
                <br/>
                <div className="flex flex-col justify-between w-2/3 h-40">
                    <label>comment</label>
                    <textarea className="h-full" name="comment" onchange={handleCommentOnchangeHandler}/>
                </div>
                <div className="flex w-2/3 justify-start my-4">
                    <button className="p-1" form="contact">Submit</button>
                </div>
            </form>
        </div>
    );
}

This is an example using useEffect. This can also be done without using it. You could also have a single onchange handler instead and set the value based on the target where the event was triggered.

CodePudding user response:

you are using react completely wrong. i highly recommend you to read react docs carefully and digging into the rendering concept. the problems i saw in your code was :

  1. the way you’re getting the input value is not good. you should define a state (controlled component) or pass a ref to get the value (uncontrolled).

  2. changing your css classes with a ref is not gonna work because it doesn’t trigger a rerender in your app (read useRef docs for more details) you should define a new state and use that state for changing your className.

  • Related