1

I'm quite unsure how to do a form validation within react.

I want to display an error message on the current page saying. However, the password validation is ignored so their is no error that shows on the page.

Password must be at least characters

Maybe i'm not using conditional rendering right

SignUp.js (snippet for demonstration purpose)

constructor(props){
 super(props);

 this.state ={
  errors: {},
}

handleSubmit(event) {
  event.preventDefault();

  const email = this.email.value;
  const password = this.password.value;

  if(password.length > 6){
    this.state.errors.password= "Password must be at least 6 characters";
  }
  const creds = {email, password}

  if(creds){
    this.props.signUp(creds);
    this.props.history.push('/');

  }
}

render() {
 return (
  <div className="container">
    <div className="row">
      <div className="col-md-6">
        <h1>Sign Up</h1>
        <form onSubmit={this.handleSubmit}>
          <div className="form-group">
            <label htmlFor="exampleInputEmail1">Email address</label>

            <input
              name="email"
              type="email"          
              className="form-control"
              id="email"
              ref={(input) => this.email = input}
              aria-describedby="emailHelp"
              placeholder="Enter email" />
            <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
          </div>
          <div className="form-group">
            <label htmlFor="exampleInputPassword1">Password</label>
            {this.password > 6 &&
               //display an error here
              <h2>{this.state.errors.password}</h2>
            }
            <input
              name="password"
              type="password"
              ref={(input) => this.password = input}
              value={this.state.password}
              className="form-control"
              id="password"    
              placeholder="Password" />

          </div>



          <button type="submit" className="btn btn-primary">Submit</button>
        </form>
      </div>

    </div>
  </div>

  );
 }

} 
Eric Thomas
  • 21
  • 1
  • 2
  • 6

4 Answers4

2

You should store the form data in your component's state. For example, have this.state.email instead of this.email. When data that is stored in a component's state is updated, a rerender is triggered. However, rerenders are not triggered for updates to plain class variables. You also directly manipulate the state when you set the errors. Instead you should use the setState method docs.

The reason you do not see your error shown on the page is because your page is not properly rerendering due to changes in the form.

Note it is also a good idea to wrap your form data variables in a formData object within state for organization. This helps keep the form's data separate from the rest of the component's state (e.g. keeps form values separate from your errors variable) and allows the form data to be passed around more easily, for example during form submission.

Here is an example of how you can reorganize things:

constructor(props){
 super(props);

 this.state = {
  formData: { // set up default form values
    email: "",
    password: "",
  },
  errors: {},
}

handleChange = event => {
  const { formData } = this.state;

  this.setState({
    formData: {
      ...formData, // leave other values unchanged
      [event.target.name]: event.target.value, // update the changed value
    }
  });
}

handleSubmit(event) {
  event.preventDefault();

  const { formData, errors } = this.state;
  const { email, password } = formData;

  if (password.length < 6) { // changed comparison to _less_ than
    this.setState({ // update errors using setState -- never directly modify a component's state
      errors: {
        ...errors,
        password: "Password must be at least 6 characters",
      }
    });
  }

  const creds = {email, password}

  if (creds.email && creds.password) { // objects are never falsey, so we need to check each field directly
    this.props.signUp(creds);
    this.props.history.push('/');

  }
}

render() {
 return (
  <div className="container">
    <div className="row">
      <div className="col-md-6">
        <h1>Sign Up</h1>
        <form onSubmit={this.handleSubmit}>
          <div className="form-group">
            <label htmlFor="exampleInputEmail1">Email address</label>

            <input
              name="email"
              type="email"          
              className="form-control"
              id="email"
              value={ this.state.formData.email } {/* control component by storing value in state and updating state when the input changes */}
              onChange={ this.handleChange }
              aria-describedby="emailHelp"
              placeholder="Enter email" />
            <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small>
          </div>
          <div className="form-group">
            <label htmlFor="exampleInputPassword1">Password</label>
            {this.state.errors.password &&
               //display an error here
              <h2>{this.state.errors.password}</h2>
            }
            <input
              name="password"
              type="password"
              value={ this.state.formData.password }
              onChange={ this.handleChange }
              className="form-control"
              id="password"    
              placeholder="Password" />

          </div>



          <button type="submit" className="btn btn-primary">Submit</button>
        </form>
      </div>

    </div>
  </div>

  );
 }

} 

Note: If your form has checkbox inputs, the shown handleChange method will not work as desired for those fields since the checked property of a checkbox input should be used instead of the value property. For simplicity, I've only included the non-checkbox case, but here is a full version of handleChange:

handleChange = event => {
    const { formData } = this.state;
    const { name, type, value, checked } = event.target;
    this.setState({
        formData: {
            ...formData,
            [name]: type === "checkbox" ? checked : value,
        }
    })
}
Henry Woody
  • 14,024
  • 7
  • 39
  • 56
  • the only this, the `{this.state.formData.password.length < 6 && //display an error here

    {this.state.errors.password}

    }` doesn't display anything
    – Eric Thomas Jan 04 '19 at 03:37
  • but it appears that it wants to show it self, it looks invisible. i can see the div move slightly down more to make space for the error. but no error appears. – Eric Thomas Jan 04 '19 at 03:38
  • @EricThomas could be a CSS issue, if you [disable CSS](https://stackoverflow.com/questions/14046738/how-to-disable-css-in-browser-for-testing-purposes) or open your browser's developer tools can you see the message? (I've also updated that section, because checking the password length is duplicate logic, it would be enough to just check if there is an error present for the password) – Henry Woody Jan 04 '19 at 03:41
  • 1
    @HenryWoody Question: I didn't understand why the `if (creds) { ... }` would work as desired. In this JSBin it works even when `email` and `password` are blank: https://jsbin.com/zelipad/edit?js,console . Please clarify. Thanks! – thanks_in_advance Apr 15 '19 at 01:14
  • 1
    @thanks_in_advance you are right. The code in this answer started with the code from the question and I only edited the parts that were specifically asked about. However, as you point out, that `if (creds)` line is basically useless since even empty objects are truthy in JS. Thanks for the comment, I'll make some edits now. – Henry Woody Apr 15 '19 at 17:10
0

This example isn't using the component state to manage the Password and Email fields.

By having them as controlled input fields you can then validate them on change and conditionally render as error message (which doesn't need to be held in state)

eg. if you wanted to show an error is the password is too short:

{ this.state.password.length < 5 && <p>Password too short</p> }

Maybe check out https://github.com/jaredpalmer/formik and look at some examples.

0

I think your code to show error show use state

{this.state.errors.password &&
           //display an error here
          <h2>{this.state.errors.password}</h2>
        }

And in submit form you should update your state

if(password.length > 6){
this.setState({ errors: { password: "Password must be at least 6 characters"} 

})
  } else {
this.setState({errors: {}})
}
duc mai
  • 1,412
  • 2
  • 10
  • 17
0

I have built custom hook for easy form validation. i think it's gonna make your life a lot easier comes to form. you can leverage your html validation knowledge.

Github: https://github.com/bluebill1049/react-hook-form

Website: http://react-hook-form.now.sh

import React from 'react'
import useForm from 'react-hook-form'

function App() {
  const { register, handleSubmit, errors } = useForm() // initialise the hook
  const onSubmit = (data) => { console.log(data) } // callback when validation pass

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstname" ref={register} /> {/* register an input */}

      <input name="lastname" ref={register({ required: true })} /> {/* apply required validation */}
      {errors.lastname && 'Last name is required.'} {/* error message */}

      <input name="age" ref={register({ pattern: /\d+/ })} /> {/* apply a Refex validation */}
      {errors.age && 'Please enter number for age.'} {/* error message */}

      <input type="submit" />
    </form>
  )
}
Bill
  • 17,872
  • 19
  • 83
  • 131