React - Add Class to Empty Input Fields

Viewed 7823

I want to add a red border only if an input is empty. I couldn't find a way to "addClass" in React so I'm using state. Right now the code will add red border to all inputs, even if it has text.

State:

this.state = {
  inputBorderError: false,
};

HTML/JSX:

<label>Name</label>
<input className={
  this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />

<label>Email</label>
<input className={
  this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />

<label>Message</label>
<textarea className={
  this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />

CSS:

.form-input-fail {
  border: 1px solid red;
}

JS:

   let inputFields = document.getElementsByClassName('form-input');

   for (var i = 0; i < inputFields.length; i++) {
      if (inputFields[i].value === '') {
        this.setState({
          inputBorderError: true,
        });
      }
    }

I see the error in my code as it's basically setting the state to true anytime it finds an empty input. I think I may be approaching this incorrectly as there's only one state. Is there a solution based on my state approach, or is there another solution?

3 Answers

Right now, you have single state-value that affects all inputs, you should consider having one for each input. Also, your inputs are not controlled, it will be harder to record and track their values for error-handling.

It is good practice to give each input tag a name property. Making it easier to dynamically update their corresponding state-value.

Try something like the following, start typing into each input, then remove your text: https://codesandbox.io/s/nervous-feynman-vfmh5

class App extends React.Component {
  state = {
    inputs: {
      name: "",
      email: "",
      message: ""
    },
    errors: {
      name: false,
      email: false,
      message: false
    }
  };

  handleOnChange = event => {
    this.setState({
      inputs: {
        ...this.state.inputs,
        [event.target.name]: event.target.value
      },
      errors: {
        ...this.state.errors,
        [event.target.name]: false
      }
    });
  };

  handleOnBlur = event => {
    const { inputs } = this.state;
    if (inputs[event.target.name].length === 0) {
      this.setState({
        errors: {
          ...this.state.errors,
          [event.target.name]: true
        }
      });
    }
  };

  handleOnSubmit = event => {
    event.preventDefault();
    const { inputs, errors } = this.state;
    //create new errors object
    let newErrorsObj = Object.entries(inputs)
      .filter(([key, value]) => {
        return value.length === 0;
      })
      .reduce((obj, [key, value]) => {
        if (value.length === 0) {
          obj[key] = true;
        } else {
          obj[key] = false;
        }
        return obj;
      }, {});

    if (Object.keys(newErrorsObj).length > 0) {
      this.setState({
        errors: newErrorsObj
      });
    }
  };

  render() {
    const { inputs, errors } = this.state;
    return (
      <div>
        <form onSubmit={this.handleOnSubmit}>
          <label>Name</label>
          <input
            className={
              errors.name ? "form-input form-input-fail" : "form-input"
            }
            name="name"
            value={inputs.name}
            onChange={this.handleOnChange}
            onBlur={this.handleOnBlur}
          />

          <label>Email</label>
          <input
            className={
              errors.email ? "form-input form-input-fail" : "form-input"
            }
            name="email"
            value={inputs.email}
            onChange={this.handleOnChange}
            onBlur={this.handleOnBlur}
          />

          <label>Message</label>
          <textarea
            className={
              errors.message ? "form-input form-input-fail" : "form-input"
            }
            name="message"
            value={inputs.message}
            onChange={this.handleOnChange}
            onBlur={this.handleOnBlur}
          />
          <button type="submit">Submit</button>
        </form>
      </div>
    );
  }
}

You are correct that there is only one state.

What you need to do is store a separate error for each input. one way to do this is with a set or array on state like state = {errors: []} and then check

<label>Name</label>
<input className={
  this.state.errors.includes('name') ? 'form-input form-input-fail' : 'form-input'
} />

<label>Email</label>
<input className={
  this.state.errors.includes('email') ? 'form-input form-input-fail' : 'form-input'
} />
} />

You should keep track of the input value in the state instead of checking for borderStyling state only.

Base on your code, you could refactor it to something like this:

// keep track of your input changes
this.state = {
  inputs: {
    email: '',
    name: '',
    comment: '',
  },
  errors: {
    email: false,
    name: false,
    comment: false,
  }
};

// event handler for input changes

handleChange = ({ target: { name, value } }) => {
    const inputChanges = {
       ...state.inputs,
       [name]: value
    }

    const inputErrors = {
       ...state.errors,
       [name]: value == ""
    }

    setState({
      inputs: inputChanges,
      errors: inputErrors,
    });
}

HTML/JSX

// the name attribut for your input

<label>Name</label>
<input name="name" onChange={handleChange} className={
  this.errors.name == "" ? 'form-input form-input-fail' : 'form-input'
} />

<label>Email</label>
<input name="email" onChange={handleChange} className={
  this.errors.email == "" ? 'form-input form-input-fail' : 'form-input'
} />

<label>Message</label>
<textarea name="comment" onChange={handleChange} className={
  this.errors.comment == "" ? 'form-input form-input-fail' : 'form-input'
} />

And if you are probably looking at implementing it with CSS and js, you can try this article matching-an-empty-input-box-using-css-and-js

But try and learn to make your component reusable and Dry, because that is the beginning of enjoying react app.

[Revised]

Related