Search code examples
javascriptreactjstailwind-cssreact-functional-component

Validate fields and show the error (if any) at tab pressing or when switching to a new field instead


Tech: ReactJS and TailwindCSS. Currently, the email field is validated and error is shown as the user types. Code:

<div className='flex w-full flex-col space-y-2'>
        <label htmlFor='email' className='text-sm text-gray-600'>
          Email address
        </label>
        <input
          type='email'
          id='email'
          autoComplete='email'
          required
          className='peer relative block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 invalid:text-pink-600 focus:z-10 focus:border-indigo-500 focus:outline-none  focus:ring-indigo-500 focus:invalid:border-pink-500 focus:invalid:ring-pink-500 sm:text-sm'
          value={email}
          onChange={e => setEmail(e.target.value)}
        />
        <p className={`invisible mt-2 text-sm text-pink-600 ${email.length > 0 ? 'peer-invalid:visible' : ''}`}>
          Please provide a valid email address.
        </p>
</div>

emailFieldValidation

Can I show it at tab pressing or when switching to a new field instead? I tried using onBlur / onFocus at

<p className={`invisible mt-2 text-sm text-pink-600 ${onBlur ? 'peer-invalid:visible' : ''}`}>

But this didn't seem to work. How to do it?


Solution

  • You need to attach an onBlur handler to the input, and use that to manipulate a state item which will indicate if the field has been "touched" or not. The way you used onBlur dosn't make sense as onBlur is a prop of input and it's a function, not a bool. You have to do the heavy lifting to get the bool that says "did they touch this field".

    const [showEmailError, setShowEmailError] = useState(false)
    
    // ...
    
    <div className='flex w-full flex-col space-y-2'>
            <label htmlFor='email' className='text-sm text-gray-600'>
              Email address
            </label>
            <input
              type='email'
              id='email'
              autoComplete='email'
              required
              className='peer relative block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 invalid:text-pink-600 focus:z-10 focus:border-indigo-500 focus:outline-none  focus:ring-indigo-500 focus:invalid:border-pink-500 focus:invalid:ring-pink-500 sm:text-sm'
              value={email}
              onChange={e => {
                  setEmail(e.target.value)
                  setShowEmailError(false)
              }}
              onBlur={e => setShowEmailError(true)}
            />
            <p className={`invisible mt-2 text-sm text-pink-600 ${email.length > 0 && showEmailError ? 'peer-invalid:visible' : ''}`}>
              Please provide a valid email address.
            </p>
    </div>
    

    Note that writing form logic like this can be very tiresome with lots of fields. I'd highly recommend you look into a popular form lib https://react-hook-form.com/ which covers all the edge cases and and already provides the behaviour you are looking for.