I have created the React js demo using React MUI. I have made a separate component for each input called InputText.js
and used this for each text box. My issue is when I used this component for any single information then it’s working fine but I have prepared one array using useState
for education and I want to set each education in a separate input box using a loop in JSX but it is not works.
I have managed the input validation using react-hook-form
with controller method.
Please check all my code and help me to fix this issue
components/InputText.js
import * as React from "react";
import { useState } from "react";
import TextField from "@mui/material/TextField";
import { PropTypes } from "prop-types";
import { Controller } from "react-hook-form";
const InputText = React.forwardRef(({ control, ...rest }, ref) => {
const [textValue, setTextValue] = useState("");
const onTextChange = (e, field) => {
setTextValue(e.target.value);
return rest.onChange(e);
};
return (
<Controller
name={rest.name}
rules={rest.rules}
control={control}
render={({ field }) => (
<TextField
fullWidth
onChange={e => {
onTextChange(e, field);
}}
label={rest.label}
type={rest.type}
value={textValue}
helperText={rest.helperText}
disabled={rest.disabled}
{...field}
ref={ref}
//onChange={onTextChange}
/>
)}
/>
);
});
InputText.defaultProps = {
type: "text",
name: "",
label: "Field",
value: "",
disabled: false,
};
InputText.propTypes = {
label: PropTypes.string,
value: PropTypes.string,
};
export default InputText;
components/AddEditUser.js
import * as React from "react";
import InputText from "./InputText.js";
import DatePicker from "./DatePicker.js";
import { useForm } from "react-hook-form";
import Grid from "@mui/material/Unstable_Grid2";
import MIButton from "./Button.js";
import { post, get, patch } from "../utils/ApiServices.js";
import { noti } from "../utils/helper.js";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNavigate, useParams } from "react-router-dom";
import dayjs from "dayjs";
import { Backdrop, CircularProgress } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
const schema = yup.object({
firstName: yup.string().required("Please enter first name"),
lastName: yup.string().required("Please enter last name"),
email: yup
.string()
.required("Please enter email")
.matches(
/^w+([.-]?w+)*@w+([.-]?w+)*(.w{2,3})+$/,
"Please enter valid email"
),
birthDate: yup
.date("Please select valid date")
.typeError("Please select valid birth date")
.required("Please enter birth date"),
});
export default function AddEditUser() {
const { id } = useParams();
const [education, setEducation] = React.useState([
{
education: "BCA",
location: "Ahmedabad",
},
{
education: "MCA",
location: "USA",
},
]);
let formData = {
firstName: "",
lastName: "",
email: "",
phone: "",
birthDate: "",
age: "",
edu: education,
};
const navigate = useNavigate();
const {
control,
setValue,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: formData,
resolver: yupResolver(schema),
});
const [age, setAge] = React.useState("");
const [loading, setloading] = React.useState(id ? true : false);
const onInputChange = (e) => {
console.log("Its parent component............ ", e);
};
const onDateAccept = (date) => {
var dob = new Date(date.$d);
var month_diff = Date.now() - dob.getTime();
var age_dt = new Date(month_diff);
var year = age_dt.getUTCFullYear();
var age = Math.abs(year - 1970);
setValue("age", age);
};
//Submit form
const onSubmit = (data) => {
let params = data;
let query = id ? patch("user/" + id, params) : post("user", params);
query
.then(({ data }) => {
noti(
"success",
"User " + (id ? "updated" : "added") + " successfully."
);
navigate("/");
})
.catch((error) => {
noti("error", error);
});
};
//Set user form data
React.useEffect(() => {
if (id) {
get("user/" + id)
.then(({ data }) => {
setValue("firstName", data.data["firstName"]);
setValue("lastName", data.data["lastName"]);
setValue("email", data.data["email"]);
setValue("phone", data.data["phone"]);
setValue("birthDate", dayjs(data.data["birthDate"]));
setloading(false);
})
.catch((error) => {
noti("error", error);
});
}
}, []);
const addEducation = () => {
formData.edu.push({
education: "Bvoc",
location: "Junagadh",
});
setEducation(formData.edu);
console.log("Add edu--> ", formData);
};
console.log(id);
return (
<>
<h2>{id ? "Edit" : "Add"} User</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<Backdrop
sx={{ color: "#5ac8fa", zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={loading}
>
<CircularProgress color="inherit" />
</Backdrop>
<Grid
container
spacing={5}
alignItems="center"
justifyContent="center"
columns={12}
style={{ marginTop: "10px", textAlign: "center" }}
>
<Grid xs={6}>
<InputText
label={"First Name"}
name="firstName"
onChange={onInputChange}
control={control}
// rules={{
// required: "Please enter first name",
// maxLength: {
// value: 10,
// message: "Please enter max 10 character only",
// },
// minLength: {
// value: 2,
// message: "Please enter min 2 character",
// },
// }}
helperText={errors.firstName?.message}
/>
</Grid>
<Grid xs={6}>
<InputText
label={"Last Name"}
name="lastName"
onChange={onInputChange}
control={control}
helperText={errors.lastName?.message}
/>
</Grid>
<Grid xs={6}>
<InputText
label={"Email"}
name="email"
onChange={onInputChange}
control={control}
helperText={errors.email?.message}
disabled={id ? true : false}
/>
</Grid>
<Grid xs={6}>
<InputText
label={"Phone"}
name="phone"
type="number"
onChange={onInputChange}
control={control}
helperText={errors.phone?.message}
/>
</Grid>
<Grid xs={6}>
<DatePicker
label={"Birth Date"}
name="birthDate"
onAccept={onDateAccept}
control={control}
helperText={errors.birthDate?.message}
disableFuture
openTo="year"
/>
</Grid>
<Grid xs={6}>
<InputText
label={"Age"}
name="age"
onChange={onInputChange}
control={control}
value={age}
disabled
/>
</Grid>
</Grid>
{formData.edu.map((row, index) => (
<Grid container spacing={5} columns={12} key={index}>
<Grid xs={6}>
{row.education}
<InputText
label={"Education"}
name={row.education}
onChange={onInputChange}
control={control}
value={row.education}
//helperText={errors.lastName?.message}
/>
</Grid>
<Grid xs={6}>
{row.location}
<InputText
label={"Location"}
name="location"
onChange={onInputChange}
control={control}
value={row.location}
helperText={errors.lastName?.message}
/>
</Grid>
</Grid>
))}
<AddIcon style={{ cursor: "pointer" }} onClick={addEducation} />
<MIButton
label={id ? "Update" : "Save"}
type="submit"
style={{ marginTop: "2%", marginBottom: "1%" }}
/>
</form>
</>
);
}
I am new to React js Hence suggestions and fixes are most welcome. Kindly please let me know if still need any further details and share your answer Hence I can fix the above issue.
Thank you!🙏🙏
2
Answers
Here is the answer!
I have used the name as name={
edu[${index}].education
} and it works perfectlyThe issue you’re facing seems to be related to how you’re handling the state for each input field using the
textValue
state in theInputText
component. Since you’re using an array of education objects and want to display each education in a separate input box using a loop, you need to manage the state of each input field individually.Here’s a modified version of your
InputText
component that is better suited for handling input fields within a loop and array data:Now, in the component where you’re rendering the education inputs, you can map over the array of education objects and render separate input fields for each education:
This way, you’re mapping over the
educations
array and rendering separate input fields for each education object. Each input field has a unique name (education_${education.id}
) to distinguish them and thedefaultValue
is set based on the education title. The state management for each input field is now handled by thereact-hook-form
library through theController
component.