MrSrv7
MrSrv7

Reputation: 781

How to conditionally render error in react js?

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 :)

Upvotes: 4

Views: 2094

Answers (4)

Abhishek
Abhishek

Reputation: 46

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

Upvotes: 1

geoffrey
geoffrey

Reputation: 2464

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.

Upvotes: 1

Lakshya Thakur
Lakshya Thakur

Reputation: 8316

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

Upvotes: 1

pouria
pouria

Reputation: 1008

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.

Upvotes: 2

Related Questions