React回调函数中更新状态变量无效,无法触发组件重渲染的问题求助
React回调函数中更新状态变量无效,无法触发组件重渲染的问题求助
我现在遇到一个React状态更新的棘手问题:在回调函数里修改状态变量后,状态并没有实际发生变化,组件也不会触发重渲染;就算偶尔触发了重渲染,状态也不是预期的样子。
目前按钮的回调函数都是正常触发的——点击Cancel或者Confirm按钮时,对应的回调都会执行,日志也能正常打印出来,但调用setShowConfirmDialog(prev => false)后,确认对话框就是纹丝不动。我特意加了useEffect来监听状态变化,结果发现showConfirmDialog根本没变化。
我怀疑这是闭包导致的「过期状态(stale state)」问题,或者我对React渲染机制的理解存在根本性误区?我试过各种方法了:用useCallback包裹回调、直接写setShowConfirmDialog(false)、把状态作为props传递,但都没解决问题。下面是我简化后的示例代码,麻烦大家帮我看看哪里出问题了。
父组件 page.js
"use client" import { useEffect, useState } from "react"; import { DeletableRow, EditableCell, ReadOnlyCell, SelectableCell } from "./Components/EditableCell"; export default function Home() { const [usersData, setUsersData] = useState([]); useEffect(() => { const dbrows = [ { id: 11, name: "Dotty", email: "dotty@mail.com" }, { id: 22, name: "Adam", email: "adam@mail.com" } ]; setUsersData(dbrows); }, []); function handleDeleteUserCB(id) { console.log("Placeholder for parent CB: handleDeleteCB: id=", id) }; let rows = usersData.map((d, i) => { let row = <DeletableRow id={d.id} printName={`${d.id} ${d.name} ${d.email}`} type="User" key={"users__" + i} parentHandleDeleteCB={handleDeleteUserCB}> <ReadOnlyCell value={i} /> <ReadOnlyCell value={d.id} /> <EditableCell value={d.name} prefix="user__name__" /> <EditableCell value={d.email} prefix="user__email__" /> <ReadOnlyCell value="Read-only text" prefix="user__email__" /> </DeletableRow > return row; }); console.debug("rowsToShow=", rows); return ( <> <h1>User list</h1> <table > <tbody> {rows} </tbody> </table> </> ); }
子组件 EditableCell.js
"use client" import React, { useCallback, useEffect, useRef, useState } from "react"; export function DeletableRow({ printName, id, children, prefix, parentHandleDeleteCB, parentHandleUpdateCB }) { const [deleteInitiated, setDeleteInitiated] = useState(false); console.log("(Re)Rendering <tr>", { printName, id, children, prefix, parentHandleDeleteCB, parentHandleUpdateCB }); return ( <tr key={prefix + id} > {/* For convenience, pass some props to all children*/ React.Children.map(children, (child) => ( React.cloneElement(child, { id, parentHandleUpdateCB }) ))} <DeleteButtonCell id={id} printName={printName} parentHandleDeleteCB={parentHandleDeleteCB} /> </tr> ); } export function EditableCell({ id, prefix, value, parentHandleUpdateCB }) { return ( <td id={prefix + id} contentEditable={true} style={{ border: "1px solid yellow", padding: "10px" }} title={`Click to change ${value} to another value."`} suppressContentEditableWarning={true} > {value} </td> ) } export function ReadOnlyCell({ value }) { return ( <td contentEditable={false} style={{ border: "1px solid grey", padding: "10px" }} title="Read only value"> {value} </td> ) } export function DeleteButtonCell({ printName, id, parentHandleDeleteCB }) { const [showConfirmDialog, setShowConfirmDialog] = useState(undefined); const [itemIdToDelete, setItemIdToDelete] = useState(null); const showConfirmDialogRef = useRef(undefined); console.log("inline showConfirmDialog=", showConfirmDialog); console.log("inline itemIdToDelete=", itemIdToDelete); useEffect(() => { console.log("useEffect showConfirmDialog=", showConfirmDialog); console.log("useEffect itemIdToDelete=", itemIdToDelete); }, [showConfirmDialog, itemIdToDelete]); const handleDeleteClick = useCallback((itemId) => { setItemIdToDelete(itemId); setShowConfirmDialog(prev => true); }, []); const confirmDeleteCB = useCallback((id, parentHandleDeleteCB) => { // Call the deletion callback in the parent component parentHandleDeleteCB(id); console.log("After parent call, which would have deleted id=", id, " Removing dialog.") // Deletion complete, remove the dialog. setShowConfirmDialog(prev => false) console.log("delete complete - dialog should close"); }, [parentHandleDeleteCB]); const cancelDeleteCB = useCallback(() => { console.log("setShowConfirmDialog(false)"); setShowConfirmDialog(prev => false), console.log("delete cancelled - dialog should close"); }, []); console.log("Rendering DeleteButtonCell", { showConfirmDialog, printName, id }); return ( <td onClick={() => handleDeleteClick(id)} title={`Delete ${printName}?`}><button> ❌ </button> {showConfirmDialog && <DeleteConfirmationDialog // setShowConfirmDialog={setShowConfirmDialog} printName={printName} id={id} cancelCB={cancelDeleteCB} confirmCB={() => confirmDeleteCB(itemIdToDelete, parentHandleDeleteCB)} /> } </td>) } export function DeleteConfirmationDialog({ printName, id, cancelCB, confirmCB, parentHandleDeleteCB }) { const onCancel = () => { console.log("onCancel"); cancelCB() }; const onUserX = () => { console.log("onX"); cancelCB() }; const onConfirm = () => { console.log("onConfirm"); confirmCB() }; console.log("Rendering DeleteConfirmationDialog", { printName, id, cancelCB, confirmCB }); console.log("printName=[", printName, "]"); return ( <div > <div style={{ border: "4px solid red" }} > <div title="Cancel - X" onClick={() => { onUserX(); }}>X</div> <h3 >Confirm Deletion of {printName} </h3> <p>Are you sure you want to delete <br /><strong>{printName}</strong>?</p> <button title="Confirm" onClick={onConfirm}>Confirm</button> <button title="Cancel" onClick={onCancel}>Cancel</button> </div> </div> ) }
备注:内容来源于stack exchange,提问作者P. Olin
相关产品推荐
相关产品推荐

