skip to Main Content

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


  1. Chosen as BEST ANSWER

    Here is the answer!

    <InputText
      label={"Education"} 
      name={`edu[${index}].education`}
      onChange={onInputChange}
      control={control}
      value={row.education}
    />
    

    I have used the name as name={edu[${index}].education} and it works perfectly


  2. The issue you’re facing seems to be related to how you’re handling the state for each input field using the textValue state in the InputText 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:

    import * as React 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) => {
    
      return (
        <Controller
          name={rest.name}
          rules={rest.rules}
          control={control}
          render={({ field }) => (
            <TextField
              fullWidth
              onChange={field.onChange}
              label={rest.label}
              type={rest.type}
              value={field.value}
              helperText={rest.helperText}
              disabled={rest.disabled}
              {...field}
              ref={ref}
            />
          )}
        />
      );
    });
    
    InputText.defaultProps = {
      type: "text",
      name: "",
      label: "Field",
      value: "",
      disabled: false,
    };
    InputText.propTypes = {
      label: PropTypes.string,
      value: PropTypes.string,
    };
    
    export default InputText;
    

    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:

    import React from "react";
    import { useForm, Controller } from "react-hook-form";
    import InputText from "./InputText";
    
    const EducationForm = () => {
      const { control, handleSubmit } = useForm();
      const [educations, setEducations] = React.useState([
        { id: 1, title: "Education 1" },
        { id: 2, title: "Education 2" },
        // ... other education objects
      ]);
    
      const onSubmit = (data) => {
        console.log(data);
      };
    
      return (
        <form onSubmit={handleSubmit(onSubmit)}>
          {educations.map((education) => (
            <Controller
              key={education.id}
              name={`education_${education.id}`} // Use a unique name for each input
              control={control}
              defaultValue={education.title} // Set initial value
              render={({ field }) => (
                <InputText
                  control={control}
                  {...field}
                  label={`Education ${education.id}`}
                  rules={{ required: true }} // Add your validation rules
                />
              )}
            />
          ))}
          <button type="submit">Submit</button>
        </form>
      );
    };
    
    export default EducationForm;
    

    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 the defaultValue is set based on the education title. The state management for each input field is now handled by the react-hook-form library through the Controller component.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search