简体   繁体   中英

React: DOM not reflecting component state in dynamic form

Here's a dynamic form for student details where new student fields can be added or deleted, using the ' + ' & the ' X ' buttons. (ie, number of student fields is decided by user). Here, App => Parent Component & StudentsFormElement => Child Component.

Problem : Any of the ' X ' buttons, when clicked, are deleting only the last student field(in DOM) and not the one which was supposed to be deleted. But most importantly, the parent component state changes correctly, with the correct student details being deleted from it. This state change is not being reflected in the DOM.

codesandbox: https://codesandbox.io/s/peaceful-spence-1j3nv?fontsize=14&hidenavigation=1&theme=dark

App component:

class App extends React.Component {
  constructor(props) {
    super(props)
    let studentsFormElementsTemp = []
    let tempSTUDENTS = {0: ["", ""]}
    this.state = {
      STUDENTS: tempSTUDENTS
    }
    studentsFormElementsTemp.push(<StudentsFormElement id="0" student={this.state.STUDENTS[0]} onStudentsChange={this.onStudentsChange} />)
    this.state = {
      studentsFormElements: studentsFormElementsTemp,
      studentsElementsIdArray: [0],
      STUDENTS: tempSTUDENTS
    } 
  }

  render() {
    return (
        <div>
            <h2 style={{textAlign: "center", display: "inline-block"}}>Students</h2><Button id="+" style={{display: "inline-block"}} variant="success" onClick={this.onStudentsChange}>+</Button>
            <form>
                {this.state.studentsFormElements}
            </form>
            <p>{JSON.stringify(this.state.STUDENTS)}</p>
        </div>
    )
  }

  onStudentsChange = (e) => {
    if (e.target.name === "studentId" || e.target.name === "studentName") { //HANDLING TYPED CHARACTERS.
      let tempSTUDENTS = this.state.STUDENTS
      if (e.target.name === "studentId") {
        tempSTUDENTS[e.target.id][0] = e.target.value
      }
      else {
        tempSTUDENTS[e.target.id][1] = e.target.value
      }
      this.setState({
        STUDENTS: tempSTUDENTS
      })
    } else { 
      let studentsFormElementsTemp = this.state.studentsFormElements
      let studentsElementsIdArrayTemp = this.state.studentsElementsIdArray
      let tempSTUDENTS = this.state.STUDENTS
      if (e.target.id === "+") { //ADDING (+) STUDENT
        tempSTUDENTS[studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1] = ["", ""]
        this.setState({
          STUDENTS: tempSTUDENTS
        })
        studentsFormElementsTemp.push(<StudentsFormElement id={studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1} student={this.state.STUDENTS[studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1]} onStudentsChange={this.onStudentsChange} />)
        studentsElementsIdArrayTemp.push(studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1)
        this.setState({
            studentsFormElements: studentsFormElementsTemp,
            studentsElementsIdArray: studentsElementsIdArrayTemp
        })
      } else { //DELETING STUDENT (X)
        let studentIndex = studentsElementsIdArrayTemp.indexOf(parseInt(e.target.id))
        studentsFormElementsTemp.splice(studentIndex, 1)
        studentsElementsIdArrayTemp.splice(studentIndex, 1)
        delete tempSTUDENTS[e.target.id]
        this.setState({
            studentsFormElements: studentsFormElementsTemp,
            studentsElementsIdArray: studentsElementsIdArrayTemp,
            STUDENTS: tempSTUDENTS
        })
      }
    }
  }
}

StudentsFormElement component:

class StudentsFormElement extends React.Component {
  render() {
    return (
      <InputGroup className="mb-3">
        <FormControl name="studentId" id={this.props.id} defaultValue={this.props.student[0]} placeholder="Id" onChange={this.props.onStudentsChange} />
        <FormControl name="studentName" id={this.props.id} defaultValue={this.props.student[1]} placeholder="Name" onChange={this.props.onStudentsChange} />
        <InputGroup.Append style={{display: "inline-block"}}>
          <Button id={this.props.id} variant="danger" onClick={this.props.onStudentsChange}>X</Button>
        </InputGroup.Append>
      </InputGroup>
    )
  }
}

Things I've tried: I've tried this.forceUpdate() just after handling 'X' button in onStudentsChange() but it doesn't make a difference.

Again, codesandbox: https://codesandbox.io/s/peaceful-spence-1j3nv?fontsize=14&hidenavigation=1&theme=dark

If you look at the code below:

<form>{this.state.studentsFormElements}</form>

I just changed to Form and it is working correctly.

You have to add a key prop to StudentsFormElement . If you open the console you can see React throwing an error. I made two changes,

  1. Line 12:
studentsFormElementsTemp.push(<StudentsFormElement id="0" key={0} student={this.state.STUDENTS[0]} onStudentsChange={this.onStudentsChange} />)
  1. Line 53:
studentsFormElementsTemp.push(<StudentsFormElement id={studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1}
          key={studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1}
          student={this.state.STUDENTS[studentsElementsIdArrayTemp[studentsElementsIdArrayTemp.length - 1] + 1]} onStudentsChange={this.onStudentsChange} />)

There are other refactorings I can point out, but they are irrelevant to the question asked.

Other refactoring as you asked,

import React from "react";
import { InputGroup, FormControl, Button, Form } from "react-bootstrap";

class App extends React.Component {
  constructor(props) {
    super(props);
    let identifier = 0;
    this.state = {
      students: [
        {
          identifier: identifier++,
          id: "",
          name: ""
        }
      ],
      identifier
    };
  }

  addStudent = () => {
    this.setState((prevState) => {
      const newStudents = [...prevState.students];
      newStudents.push({
        identifier: prevState.identifier,
        id: "",
        name: ""
      });
      return {
        identifier: prevState.identifier + 1,
        students: newStudents
      };
    });
  };

  onDeleteStudent = (identifier) => {
    console.log(identifier);
    const filteredStudents = this.state.students.filter(
      (student) => student.identifier !== identifier
    );
    this.setState({
      students: filteredStudents
    });
  };

  onInputChange = (event, fieldName, identifier) => {
    const newStudents = [...this.state.students];
    newStudents.forEach((student) => {
      if (student.identifier === identifier) {
        student[fieldName] = event.target.value;
      }
    });
    this.setState(newStudents);
  };

  render() {
    return (
      <div>
        <h2 style={{ textAlign: "center", display: "inline-block" }}>
          Students
        </h2>
        <Button
          id="+"
          style={{ display: "inline-block" }}
          variant="success"
          onClick={this.addStudent}
        >
          +
        </Button>
        <Form>
          {this.state.students.map((student, index) => (
            <StudentsFormElement
              key={student.identifier}
              student={student}
              onInputChange={this.onInputChange}
              onDeleteStudent={this.onDeleteStudent}
            />
          ))}
        </Form>
        <p>{JSON.stringify(this.state.students)}</p>
      </div>
    );
  }
}

class StudentsFormElement extends React.Component {
  render() {
    const { identifier, id, name } = this.props.student;
    return (
      <InputGroup className="mb-3">
        <FormControl
          name="id"
          defaultValue={id}
          placeholder="Id"
          onChange={(event) => {
            this.props.onInputChange(event, "id", identifier);
          }}
        />
        <FormControl
          name="name"
          defaultValue={name}
          placeholder="Name"
          onChange={(event) => {
            this.props.onInputChange(event, "name", identifier);
          }}
        />
        <InputGroup.Append style={{ display: "inline-block" }}>
          <Button
            variant="danger"
            onClick={() => {
              this.props.onDeleteStudent(identifier);
            }}
          >
            X
          </Button>
        </InputGroup.Append>
      </InputGroup>
    );
  }
}

export default App;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM