You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>&nbsp;❌&nbsp;</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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 18:18:02