4

I have a register form component in reactjs with a material UI component. I have an array of objects that contains the value of props for the TextField component. If the field value is empty, I want to conditionally show the "Cannot be empty" helper text. E.g., if the email is not entered, but the name is entered, I should show the only Email that cannot be empty error text.

Errors I am facing: I am able to show the error, but even if one input is empty, an error is displayed in all fields.

Expected: Only corresponding TextField's helper text should be displayed if there were any error

I have tried - CodeSandBox link https://codesandbox.io/s/infallible-joliot-6vkrq?file=/src/App.jsfile=/src/App.js

In what way can I improve this? Any help is appreciated. Thanks :)

1
  • Alternative solution: Demo Commented Mar 18, 2021 at 11:49

4 Answers 4

2

It's because you're using a boolean. You should use an object instead:

import { Grid, TextField } from "@material-ui/core";
import { useState } from "react";
import "./styles.css";

export default function App() {
  const [inputErrors, setInputErrors] = useState({}); // PLURALIZED AND INITIALIZED WITH AN EMPTY OBJECT
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleChange = (event, index, setValue, setError) => {
    let value = event.target.value;
    if (!value) {
      setError(state => ({
        ...state,
        [event.target.name]: true
      }));
    } else {
      setError(state => ({
        ...state,
        [event.target.name]: false
      }));
      setValue(value);
    }
  };

  const arrObj = [
    {
      name: "Name",
      input: "Please enter your name",
      onChange: (event, index) =>
        handleChange(event, index, setName, setInputErrors),
      helperText: inputErrors.name && "*Name is Required"
    },
    {
      name: "Email",
      input: "enter your email address",
      onChange: (event, index) =>
        handleChange(event, index, setEmail, setInputErrors),
      helperText: inputErrors.email && "*Email is Required"
    }
  ];

  return (
    <div className="App">
      {arrObj.map((item, index) => {
        let { onChange, input, helperText } = item;
        return (
          <Grid key={index} item xs={12} style={{ textAlign: "center" }}>
            <TextField
              name={item.name.toLowerCase()}
              placeholder={input}
              required
              onChange={(event) => onChange(event, index)}
              helperText={helperText}
            />
          </Grid>
        );
      })}
    </div>
  );
}


Here we assign a "name" attribute on the input fields and use it in the code as the key for the error.

Sign up to request clarification or add additional context in comments.

Comments

1

You're using a single Boolean state inputError to decide errors for both the name and email field so that's why you see the error. There is no way to distinguish from which field the error occured.

Instead of that you can have a dedicated inputError object which acts as a Hashmap/Dictionary for your input fields with true/false indicating whether a particular field has error.

Whole code for reference :-

import { Grid, TextField } from "@material-ui/core";
import { useState } from "react";
import "./styles.css";

export default function App() {
  const [inputError, setInputError] = useState({ name: false, email: false });
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleChange = (event, index, setValue, setError) => {
    let { value, name } = event.target;
    name = name.toLowerCase();
    if (!value) setError({ ...inputError, [name]: true });
    else {
      setError({ ...inputError, [name]: false });
      setValue(value);
    }
  };
  const arrObj = [
    {
      name: "Name",
      input: "Please enter your name",
      onChange: (event, index) =>
        handleChange(event, index, setName, setInputError),
      helperText: inputError.name && "*Name is Required"
    },
    {
      name: "Email",
      input: "enter your email address",
      onChange: (event, index) =>
        handleChange(event, index, setEmail, setInputError),
      helperText: inputError.email && "*Email is Required"
    }
  ];

  return (
    <div className="App">
      {arrObj.map((item, index) => {
        let { onChange, input, helperText, name } = item;
        return (
          <Grid key={index} item xs={12} style={{ textAlign: "center" }}>
            <TextField
              name={name}
              placeholder={input}
              required
              onChange={(event) => onChange(event, index)}
              helperText={helperText}
            />
          </Grid>
        );
      })}
    </div>
  );
}

Codesandbox Link

Comments

1

You could factorise that common logic by wrapping TextField in a component which would manage its own state. It would simplify your code greatly.

import { Grid, TextField } from "@material-ui/core";
import { useState } from "react";
import "./styles.css";

const RequiredTextField = ({item:{ input, helperText }, value}) => {
  const [inputValue, setValue] = useState(value);
  const [inputError, setInputError] = useState(true);

  const onChange = ({target:{value}}) => {
    setInputError(!value);
    setValue(value);
  }

  return <TextField
    placeholder={input}
    required
    value={inputValue}
    onChange={onChange}
    helperText={inputError && helperText}
  />
}

export default function App() {
  const arrObj = [
    {
      name: "Name",
      input: "Please enter your name",
      helperText: "*Name is Required"
    },
    {
      name: "Email",
      input: "enter your email address",
      helperText: "*Email is Required"
    }
  ];

  return (
    <div className="App">
      {arrObj.map((item, index) =>
          <Grid key={index} item xs={12} style={{ textAlign: "center" }}>
            <RequiredTextField item={item} value="" />
          </Grid>
      )}
    </div>
  );
}

Note that I don't use material UI. Make sure you are not reinventing the wheel. I would be surprised that this is not already a feature.

3 Comments

Thanks.A small suggestion , const [inputError, setInputError] = useState(true); shows the error whenever the component renders. So I think it should be false
Yeah I thought that if the user only knows about the field being reqired after the fact, it would not be user friendly. But this is because your errors are "polite" and look more like hints actually. And frankly I like this better. Had they been bold and red and nasty I would have set it to false. This can be seen as a symptom that you should have both hints and errors if you insist on displaying something after the fact. It all depends how you want to communicate with the user.
well, what you are telling also a good approach. Only errors should be displayed only if anything fails. As you said, these are just like hints. Thanks :)
1
import { Grid, TextField } from "@material-ui/core";
import { useState } from "react";
import "./styles.css";

export default function App() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [mobile, setMobile] = useState("");
  let inputErrors = {
    name: !name && "Name is Required",
    email: !email && "Email is Required",
    mobile: !mobile && "Phone No is Required"
  };
  const handleChange = ({ target }, setValue) => setValue(target.value);
  const arrObj = [
    {
      name: "Name",
      input: "Please enter your name",
      onChange: (event) => handleChange(event, setName),
      helperText: inputErrors.name && "*Name is Required"
    },
    {
      name: "Email",
      input: "enter your email address",
      onChange: (event) => handleChange(event, setEmail),
      helperText: inputErrors.email && "*Email is Required"
    },
    {
      name: "Mobile",
      input: "enter your mobile number",
      onChange: (event) => handleChange(event, setMobile),
      helperText: inputErrors.mobile && "*Phone No is Required"
    }
  ];

  return (
    <div className="App">
      {arrObj.map((item, index) => {
        let { onChange, input, helperText } = item;
        return (
          <Grid key={index} item xs={12} style={{ textAlign: "center" }}>
            <TextField
              name={item.name.toLowerCase()}
              placeholder={input}
              required
              onChange={(event) => onChange(event, index)}
              helperText={helperText}
            />
          </Grid>
        );
      })}
    </div>
  );
}

Instead of multiple state, you can make a variable that depends on a state. And that is what I implemented in this code. Also, I think it looks more readable.

Ps: I'm answering for first time on Stackoverflow so if anything is wrong I'm sorry

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.