2

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?

2
  • Hi Eric, try out the codesandbox in my solution, I think it should cover all the bases. Let me know if you have any questions. Commented Jun 18, 2019 at 21:22
  • @ChristopherNgo I'm looking into adding simple email validation. Have a thread on this, could use your expertise: stackoverflow.com/questions/56676342/… Commented Jun 19, 2019 at 21:49

3 Answers 3

4

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>
    );
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

what does the three dots ... represent?
it's called a spread operator. It basically copies the state to another object.
@EricNguyen sorry about that. Yes, essentially it just means you want to take the existing key-value pairs from an existing object and use them in your new object. Let me know if you have any other questions :)
@ChristopherNgo why do you have "const { inputs, errors } = this.state;" in render function?
@EricNguyen that is called object destructuring. You take a key from an object and use it to declare a new variable, assigning it the value from the original key-value pair. This is essentially equivalent to const inputs = this.state.inputs and const errors = this.state.errors
|
0

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'
} />
} />

Comments

0

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]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.