DEV Community

Cover image for Simple and responsive react forms with validation
anes
anes

Posted on

Simple and responsive react forms with validation

You can find a live demo here and the source code here

What is this about?

When making a website, every software developer runs into this request earlier or later: "Yeah so here we need a login form. Please make it responsive and pretty". Now you are trying to use your best knowledge of media queries to try and make it response. What if I told you that it was a lot easier than you thought? In this tutorial we will be going over how to make a pretty, response and validated form in reactJS.

What will we be going over

To make our form we will use reactJS, bootstrap and regular css. The first part will mainly focus on creating and designing the form, then we will implement client-sided validations and at the end we will add a live updating password strength-meter to show the user how strong his password is.
What we won't be going over is how to validate the incoming data server-sided.

Getting started

First we initialise our react-app by typing



npx create-react-app client-side-validations


Enter fullscreen mode Exit fullscreen mode

Then we will also want to add bootstrap. For that we execute following in our console:



npm install react-bootstrap bootstrap


Enter fullscreen mode Exit fullscreen mode

We will all do it on the homepage, so we wont need rails router or anything like that. That also means,that me need to clear out our function App() { /*...*/ } that it looks like this:



import './App.css';

function App() {
  return ();
}

export default App;


Enter fullscreen mode Exit fullscreen mode

Basic form design

For our form design we will make use of bootstrap react components. In our case, we will use the <Form> component. For responsiveness we will also use <Container> inside the form and wrap all of that in a form-wrapper to center the form in the end:



function App() {
  return (
    <div className='form-wrapper'>
      <Form>
        <Container fluid>

        </Container>
      </Form>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

And then we style it:



div.form-wrapper {
  height: 100vh;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
}

form {
  border-radius: 10px;
  box-shadow: 0 0 5px 0 hsl(0, 0%, 74%);
  padding: 20px;
}


Enter fullscreen mode Exit fullscreen mode

Then we can add our rows and their inputs. For our case we will use Bootstraps <FloatingLabel> components. For the name we want to have both inputs on the same height, as long as the form is big enough for that, so we wrap both of those in a <Col sm={6}> tag. The sm={6} makes the Columns take up 50% of the width as long as we are over a certain threshold which Bootstrap calls sm. When we go under that our input boxes take up full width. The code I used looks like this. Next we add our css:



button.btn.btn-primary {
  background-color: white;
  color: black;
  border: none;
  transition: all 0.3s ease;
  box-shadow: 0 0 5px 0 hsl(0, 0%, 74%);
}

button.btn.btn-primary:hover {
  color: black;
  background-color: white;
  transform: scale(1.03);
  box-shadow: 0 0 10px 0 hsl(0, 0%, 74%);
}

.form-control {
  border: 1px solid hsl(0, 0%, 74%);
  border-radius: 5px;
  box-shadow: none;
  transition: all 0.3s ease;
}

.form-control:focus {
  box-shadow: 0 0 10px 0 hsl(0, 0%, 74%);
  border: none;
}

div.form-floating > .form-control {
  height: 55px;
}


Enter fullscreen mode Exit fullscreen mode

Here you have a screenshot:
Screenshot of form

Validations

Next, we will implement our validations for the form. Bootstrap also offers us validations and error messages out of the box, which we will use. First we need to initialise a new state variable:



const [validated, setValidated] = useState(false);


Enter fullscreen mode Exit fullscreen mode

and pass that as the form validated attribute. We also disable the default html validators with noValidate:



<Form noValidate validated={validated}>


Enter fullscreen mode Exit fullscreen mode

Next we create a function that is called upon submission. That function should check if our form is valid. If it isn't it should abort the submission:



const handleSubmit = (event) => {
  const form = event.currentTarget;
  if (form.checkValidity() === false) {
    event.preventDefault();
    event.stopPropagation();
  }
   setValidated(true);
};


Enter fullscreen mode Exit fullscreen mode

Now we can actually just add our validators in a very similar way to what we would do if it was regular html:



<FloatingLabel controlId='firstnamLabel' label='First name'>
  <Form.Control 
    type='text'
    placeholder='First name'
    required
  />
</FloatingLabel> 


Enter fullscreen mode Exit fullscreen mode

There is also the possibility for RegEx, which we will use for our email and password:



<Form.Group className='mb-3' controlId='formBasicEmail'>
              <FloatingLabel controlId='emailLabel' label='Enter email'>
  <Form.Control
    type='email'
    placeholder='Enter email'
    required
    pattern='^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
  />
</FloatingLabel>
  <Form.Text className='text-muted' focus>
    We'll (hopefully) never share your email with anyone else.
  </Form.Text>
</Form.Group>


Enter fullscreen mode Exit fullscreen mode

The regex password looks like following:



^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$


Enter fullscreen mode Exit fullscreen mode

Maybe you already realised that the icons 'travel' before they actually arrive where they should be. That is because we set a transition time before. We can overwrite that when the field has been validated:



.was-validated .form-control {
  transition: none;
}


Enter fullscreen mode Exit fullscreen mode

Check if password and confirmation match

Next we want to check if our password and confirmation are the same. This is going to be a bit more complicated. What we can do is create a new state variable, that holds the password and its confirmation:



const [password, setPassword] = useState('')
const [confirmation, setConfirmation] = useState('')


Enter fullscreen mode Exit fullscreen mode

Now in our input fields we add an onChange to update the password and confirmation:



<Form.Control
  type='password'
  placeholder='Password'
  required
  pattern='^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$'
  onChange={(e) => setPassword(e.target.value)}
/>


Enter fullscreen mode Exit fullscreen mode

Don't forget to do the same for the confirmation field. Then we need to compare them in our handleSubmit:



const handleSubmit = (event) => {
  //...
  if (password !== confirmation) {
    event.preventDefault();
    event.stopPropagation();
  }
//...
}


Enter fullscreen mode Exit fullscreen mode

Now we need to also display this somehow. What we can do is an error message that we conditionally render. For that we just create the message, but we also give it a ref. You can style it how you want, but it is important that you give it an inline style of display: 'none':



<p
  style={{ color: 'red', display: 'none' }}
  ref={confirmationError}
>
  Password and confirmation are not the same
</p>


Enter fullscreen mode Exit fullscreen mode

Next we initialise our ref:



const confirmationError = useRef(null);


Enter fullscreen mode Exit fullscreen mode

And then we also change our if statement:



if (password !== confirmation) {
  event.preventDefault();
  event.stopPropagation();
  confirmationError.current.style.display = null;
} else {
  confirmationError.current.style.display = 'none';
}


Enter fullscreen mode Exit fullscreen mode

If we now try entering two different passwords it will display our message as it should:
Screenshot of confirmation being different

Live updating password strength meter

Now we can finally create the live updating password strength meter. We just want to have a progress bar that fills up the more secure a users password becomes. Step one is changing our onChange in our password field to call a function:



<Form.Control
  type='password'
  placeholder='Password'
  required
  pattern='^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$'
  onChange={(e) => handlPasswordChange(e.target.value)}
/>


Enter fullscreen mode Exit fullscreen mode

Then we need to add a progress bar below the FloatingLabel, where the bar itself has a ref:



<div
  style={{
    height: '24px',
    marginTop: '5px',
    backgroundColor: 'hsl(0, 0%, 74%)',
    borderRadius: '5px',
  }}
>
  <div
    ref={progressBar}
    style={{
      height: '100%',
      borderRadius: '5px',
    }}
   ></div>
</div>


Enter fullscreen mode Exit fullscreen mode

Now we can finally create our function to dynamically display the password strength. That goes through a few criteria to give points: a normal letter is one point, a number is 2 points and a special character is 3 points. With that we can say, that 33 points are very secure. That we can do like this:



const handlePasswordChange = (password) => {
    setPassword(password);
    const letterMatch = (password.match(/[a-z, A-Z]/g) || []).length;
    const numberMatch = (password.match(/[0-9]/g) || []).length;
    const specialMatch = (password.match(/[#?!@$%^&*-]/g) || []).length;

    const strength = letterMatch + numberMatch * 2 + specialMatch * 3;
    progressBar.current.style.width = `${strength * 3}%`;
    let color = 'red';
    if (strength > 10) {
      color = 'orange';
    }
    if (strength > 26) {
      color = 'green';
    }
    progressBar.current.style.backgroundColor = color;
  };


Enter fullscreen mode Exit fullscreen mode

What exactly is happening up there? Let us go though it slowly. First we set the state variable to the new password. Then we count the amount of letters, numbers and special characters with regex. The strength we calculate next by taking the amount of matches multiplied with our weights. Next we multiply the points by 3 to set the width of our progress bar. The if statements just set a threshold at how much points what colour our progress bar gets.
If we look at the final product, we can see that it looks quite good:
Screenshot of the form

Top comments (0)