Ritesh Gupta
Ritesh Gupta

Reputation: 23

React useState hook does not shows correct state of array

I have two functional components and from parent component I am creating set of controls dynamically. Based on each item created I want to delete them but every time last one is getting deleted. For example three rows created when I delete second or first on,last one was getting deleted.

Education.jsx

function Education(props) {
  const blankEdu = { id: 0, name: "", percentage: "", year: "" };
  const [eduState, setEduState] = useState([{ ...blankEdu }]);

  const addEducation = () => {
    setEduState([...eduState, { ...blankEdu }]);
  };

  function handleRemove(index) {
    console.log(index);
    if (eduState.length != 1) {
      const updatedEdu = [...eduState];
      updatedEdu.splice(index, 1);
      setEduState([...updatedEdu]);
    }
  }

  const handleEducationChange = (index, e, c) => {
    const updatedEdu = [...eduState];
    updatedEdu[index][c] = e.target.value;
    updatedEdu[index]["id"] = index;
    setEduState(updatedEdu);
  };
  return (
    <div>
      <div className="shadow p-3 mb-5 bg-white rounded">
        Final Step: Education
      </div>
      {eduState.map((val, idx) => (
        <div
          key={idx}
        >
          <EducationInput
            key={`edu-${idx}`}
            idx={idx}
            handleEducationChange={handleEducationChange}
          />
          {eduState.length > 1 ? (
            <Button variant="danger" onClick={() => handleRemove(idx)}>
              Remove Course
            </Button>
          ) : null}
        </div>
      ))}
      <Button variant="outline-info" onClick={addEducation}>
        Add New Degree
      </Button>
    </div>
  );
}

export default Education;

EducationInput.jsx

const EducationInput = ({ idx, handleEducationChange }) => {
  return (
    <div key={`edu-${idx}`} id={`edu-${idx}`}>
      <span className="border border-success">
        <Form>
          <Form.Group as={Row}>
            <Form.Label column sm={3}>
              {`Course #${idx + 1}`}:
            </Form.Label>
            <Col sm={5}>
              <input
                type="text"
                onChange={e => handleEducationChange(idx, e, "name")}
              />
            </Col>
          </Form.Group>
          <Form.Group as={Row}>
            <Form.Label column sm={3}>
              Passing Year:
            </Form.Label>
            <Col sm={5}>
              <input
                type="text"
                onChange={e => handleEducationChange(idx, e, "year")}
              />
            </Col>
          </Form.Group>
        </Form>
      </span>
    </div>
  );
};

export default EducationInput;

I checked and verified value of updatedEdu by printing on console. It is giving correct output on console but setEduState function does not updating properly on UI, don't know why.

Upvotes: 1

Views: 67

Answers (1)

SuleymanSah
SuleymanSah

Reputation: 17888

You are depending the index of the item, but indexes are changing when you add or remove elements, so they are not reliable.

You need to generate an automatic unique id when creating a new education. For example uuid package is popular for this task.

I refactored your code a little bit to make it work:

Education:

import React, { useState } from "react";
import EducationInput from "./EducationInput";
import Button from "react-bootstrap/Button";
import uuidv4 from "uuid/v4";

function Education(props) {
  const blankEdu = { id: "", name: "", percentage: "", year: "" };
  const [eduState, setEduState] = useState([{ ...blankEdu }]);

  const addEducation = () => {
    setEduState([...eduState, { ...blankEdu, id: uuidv4() }]);
  };

  function handleRemove(id) {
    console.log(id);
    if (eduState.length > 1) {
      const updatedEdus = eduState.filter(edu => edu.id !== id);
      setEduState(updatedEdus);
    }
  }

  const handleEducationChange = (id, field, value) => {
    console.log(field, value);
    let updatedEducations = eduState.map(edu => {
      if (edu.id === id) return edu;

      edu[field] = value;
      return edu;
    });

    setEduState(updatedEducations);
  };
  return (
    <div>
      <div className="shadow p-3 mb-5 bg-white rounded">
        Final Step: Education
      </div>
      {eduState.map(val => (
        <div key={val.id}>
          <EducationInput
            key={`edu-${val.id}`}
            idx={val.id}
            handleEducationChange={handleEducationChange}
          />
          {eduState.length > 1 ? (
            <Button variant="danger" onClick={() => handleRemove(val.id)}>
              Remove Course
            </Button>
          ) : null}
        </div>
      ))}
      <Button variant="outline-info" onClick={addEducation}>
        Add New Degree
      </Button>
      <br />
      <br />
      Educations in json:{JSON.stringify(eduState)}
    </div>
  );
}

export default Education;

EducationInput

import React from "react";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";

const EducationInput = ({ idx, handleEducationChange }) => {
  return (
    <div key={`edu-${idx}`} id={`edu-${idx}`}>
      <span className="border border-success">
        <Form>
          <Form.Group as={Row}>
            <Form.Label column sm={3}>
              {`Course #${idx + 1}`}:
            </Form.Label>
            <Col sm={5}>
              <input
                type="text"
                name="name"
                onChange={e =>
                  handleEducationChange(idx, e.target.name, e.target.value)
                }
              />
            </Col>
          </Form.Group>
          <Form.Group as={Row}>
            <Form.Label column sm={3}>
              Passing Year:
            </Form.Label>
            <Col sm={5}>
              <input
                type="text"
                name="year"
                onChange={e =>
                  handleEducationChange(idx, e.target.name, e.target.value)
                }
              />
            </Col>
          </Form.Group>
        </Form>
      </span>
    </div>
  );
};

export default EducationInput;

Codesandbox

Upvotes: 1

Related Questions