In the student (child) component
useEffect
hook will update the parent array via handleStudentsChange
(the function provided by the parent component). In the student (parent) component
handleStudentsChange
function uses the useCallback
hook definition. However, it doesn't seem to work. Questions/Questions
handleStudentsChange
will run indefinitelySee 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粉9550636622024-02-27 00:32:58
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 Student
The component re-renders and then calls useEffect
again, infinite loop.
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>