Prevent infinite re-rendering caused by child component updates to parent component array
P粉197639753
P粉197639753 2024-02-26 10:26:24
0
1
502

In the student (child) component

  • When the value of the variable changes, the useEffect hook will update the parent array via handleStudentsChange (the function provided by the parent component).

In the student (parent) component

  • Present student (child) component list
  • To prevent infinite loops, the handleStudentsChange function uses the useCallback hook definition. However, it doesn't seem to work.

Questions/Questions

  • Once a change occurs, handleStudentsChange will run indefinitely
  • Why is this? and how to fix it?
  • Note: I don’t need the onSubmit button

See the code here: I am CodeSandBox link

Student.tsx(Children)

import React, { useState, useEffect, useRef } from "react";
import TextField from "@mui/material/TextField";

interface student {
  firstName: string;
  lastName: string;
  grade: number;
}

interface studentProps {
  id: number;
  firstName: string;
  lastName: string;
  grade: number;
  handleStudentsChange: (index: number, student: student) => void;
}

function Student(props: studentProps) {
  const [firstName, setFirstName] = useState(props.firstName);
  const [lastName, setLastName] = useState(props.lastName);
  const [grade, setGrade] = useState(props.grade);

  useEffect(() => {
    handleStudentsChange(id, {
      firstName: firstName,
      lastName: lastName,
      grade: grade
    });
  }, [firstName, lastName, grade, props]);

  return (
    <>
      <TextField
        label="firstName"
        onChange={(event) => setFirstName(event.target.value)}
        value={firstName}
      />
      <TextField
        label="lastName"
        onChange={(event) => setLastName(event.target.value)}
        value={lastName}
      />
      <TextField
        label="grade"
        onChange={(event) => setGrade(+event.target.value)}
        value={grade}
      />
    </>
  );

Students.tsx(parent)

import React, { useState, useCallback } from "react";
import Student from "./Student";

interface student {
  firstName: string;
  lastName: string;
  grade: number;
}

export default function Students() {
  const [students, setStudents] = useState<student[]>([
    { firstName: "Justin", lastName: "Bieber", grade: 100 },
    { firstName: "Robert", lastName: "Oppenhiemer", grade: 100 }
  ]);

  const handleStudentsChange = useCallback(
    (index: number, updatedStudent: student) => {
      // console.log(index) //I only want this to rerender when the value change however it turn into an infinity loop
      setStudents((prevStudents) => {
        const updatedStudents = [...prevStudents];
        updatedStudents[index] = updatedStudent;
        return updatedStudents;
      });
    },
    []
  );

  return (
    <>
      {students.map((student, index) => {
        return (
          <Student
            key={index}
            id={index}
            firstName={student.firstName}
            lastName={student.lastName}
            grade={student.grade}
            handleStudentsChange={(index: number, newStudent: student) =>
              handleStudentsChange(index, newStudent)
            }
          />
        );
      })}
    </>
  );
}

As shown in the code above, I tried using React.memo on the student (child) component and useCallback on the handleStudentsChange, hopefully Able to prevent infinite loops. However, the infinite loop continues.

P粉197639753
P粉197639753

reply all(1)
P粉955063662

question

handleStudentsChange doesn't just run once infinitely when a change occurs - it runs infinitely from the first render. This is because the Student component has a useEffect that calls handleStudentsChange, which updates the state in the Students component, causing StudentThe component re-renders and then calls useEffect again, infinite loop.

solution

You need to call handleStudentsChange only after updating the input, not after every render. I've included an example below that updates the state in Students after the blur event is fired from the input. For a smarter (and more complex) approach, you could compare props and state to decide if an update is needed, but I'll let you figure that out yourself.

const { Fragment, StrictMode, useCallback, useEffect, useState } = React;
const { createRoot } = ReactDOM;
const { TextField } = MaterialUI;

function Student(props) {
  const [firstName, setFirstName] = useState(props.firstName);
  const [lastName, setLastName] = useState(props.lastName);
  const [grade, setGrade] = useState(props.grade);
  const handleStudentsChange = props.handleStudentsChange;
  
  const onBlur = () => {
    handleStudentsChange(props.id, {
      firstName,
      lastName,
      grade,
    });
  };

  return (
    <Fragment>
      <TextField
        label="firstName"
        onBlur={onBlur}
        onChange={(event) => setFirstName(event.target.value)}
        value={firstName}
      />
      <TextField
        label="lastName"
        onBlur={onBlur}
        onChange={(event) => setLastName(event.target.value)}
        value={lastName}
      />
      <TextField
        label="grade"
        onBlur={onBlur}
        onChange={(event) => setGrade( event.target.value)}
        value={grade}
      />
    </Fragment>
  );
}

function Students() {
  const [students, setStudents] = useState([
    { firstName: "Justin", lastName: "Bieber", grade: 100 },
    { firstName: "Robert", lastName: "Oppenhiemer", grade: 100 }
  ]);

  const handleStudentsChange = useCallback(
    (index, updatedStudent) => {
      // console.log(index) // I only want this to rerender when the value change however it turn into an infinity loop
      
      console.log({ updatedStudent });

      setStudents((prevStudents) => {
        const updatedStudents = [...prevStudents];
        updatedStudents[index] = updatedStudent;
        return updatedStudents;
      });
    },
    []
  );

  return (
    <Fragment>
      {students.map((student, index) => {
        return (
          <Student
            key={index}
            id={index}
            firstName={student.firstName}
            lastName={student.lastName}
            grade={student.grade}
            handleStudentsChange={(index, newStudent) =>
              handleStudentsChange(index, newStudent)
            }
          />
        );
      })}
    </Fragment>
  );
}

function App() {
  return (
    <div className="App">
      <Students />
    </div>
  );
}

const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@mui/material@latest/umd/material-ui.production.min.js"></script>
<div id="root"></div>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template