Reputation: 781
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
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
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
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>
);
}
Upvotes: 1
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