Home  >  Q&A  >  body text

Prevent infinite re-rendering caused by child component updates to parent component array

In the student (child) component

In the student (parent) component

Questions/Questions

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粉197639753P粉197639753258 days ago475

reply all(1)I'll reply

  • P粉955063662

    P粉9550636622024-02-27 00:32:58

    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>

    reply
    0
  • Cancelreply