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

React中DataTable二次初始化失败,切换不同行数数据报错

React中DataTable动态更新行数时的异常问题及修复方案

问题描述

在React项目中使用jQuery DataTable实现日期选择后动态填充表格数据时,出现以下异常:

  • 首次选择行数相同的日期(如5月3日和5月2日)切换无问题
  • 切换到行数更少的日期(如5月1日)时抛出错误;若首次选择行数少的日期再切换到行数多的日期,页面直接崩溃,行数变化后表格无法正常更新

已尝试的无效修复方案

  • 日期变更时销毁并清空表格
  • 添加代码手动移除表头和表体:$("#example thead").remove(); $('#example tbody').remove();

报错信息

NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

问题根源分析

  1. DOM选择器错误:代码中使用$("#example")操作表格,但实际表格id为investorTable,导致无效的DOM操作
  2. 手动DOM操作与React虚拟DOM冲突:直接通过jQuery移除thead/tbody,破坏了React对DOM的管理,引发节点移除异常
  3. 初始化时机不可靠:使用setTimeout延迟初始化DataTable,无法确保与React的渲染周期同步
  4. 行key不唯一:使用数组index作为表格行的key,数据更新时可能导致React复用错误的DOM节点
  5. 空数据处理缺失:未处理无数据时的DataTable销毁逻辑

修复方案

1. 修正DOM选择器

确保所有表格操作使用正确的id investorTable

2. 让React完全控制表格DOM

移除手动操作thead/tbody的代码,由React通过state自动渲染表格内容

3. 同步DataTable生命周期与React渲染

在useEffect中,确保在React完成表格渲染后再初始化DataTable,移除不可靠的setTimeout,并在数据清空时销毁表格

4. 使用唯一行key

将表格行的key从index改为数据中唯一标识(如结合name和group_name)

5. 完善空数据处理

当investorData为空时,销毁已存在的DataTable实例

修复后的完整代码

import React, { useState, useEffect } from 'react';
import { Row, Col, Card, Container, CardHeader, CardBody, FormGroup, Label, Input, Form, Table } from 'reactstrap';
import 'datatables.net-dt/css/dataTables.dataTables.css';
import $ from 'jquery';
import 'datatables.net';

const StockSummary = () => {
  const [stockDates, setStockDates] = useState([]);
  const [selectedDate, setSelectedDate] = useState('');
  const [investorData, setInvestorData] = useState([]);

  // 模拟加载日期选项
  useEffect(() => {
    const mockDates = [
      { stock_date: '2024-05-01' },
      { stock_date: '2024-05-02' },
      { stock_date: '2024-05-03' },
    ];
    setStockDates(mockDates);
  }, []);

  // 日期变更时模拟获取数据
  const handleDateChange = (e) => {
    const date = e.target.value;
    setSelectedDate(date);

    if (date === '2024-05-01') {
      setInvestorData([
        { name: 'Alice', number_of_shares: 100, category_name: 'Retail', group_name: 'A' },
        { name: 'Bob', number_of_shares: 150, category_name: 'Retail', group_name: 'A' },
        { name: 'Charlie', number_of_shares: 200, category_name: 'Institutional', group_name: 'B' },
        { name: 'David', number_of_shares: 250, category_name: 'Retail', group_name: 'C' },
        { name: 'Eva', number_of_shares: 300, category_name: 'Institutional', group_name: 'C' },
      ]);
    } else if (date === '2024-05-02') {
      setInvestorData([
        { name: 'Frank', number_of_shares: 120, category_name: 'Retail', group_name: 'A' },
        { name: 'Grace', number_of_shares: 130, category_name: 'Retail', group_name: 'B' },
        { name: 'Hank', number_of_shares: 140, category_name: 'Institutional', group_name: 'C' },
        { name: 'Ivy', number_of_shares: 150, category_name: 'Retail', group_name: 'D' },
        { name: 'Jack', number_of_shares: 160, category_name: 'Retail', group_name: 'E' },
        { name: 'Karen', number_of_shares: 170, category_name: 'Retail', group_name: 'F' },
        { name: 'Leo', number_of_shares: 180, category_name: 'Institutional', group_name: 'G' },
      ]);
    } else if (date === '2024-05-03') {
      setInvestorData([
        { name: 'Frank 1', number_of_shares: 1200, category_name: 'Retail', group_name: 'A' },
        { name: 'Grace', number_of_shares: 130, category_name: 'Retail', group_name: 'B' },
        { name: 'Hank', number_of_shares: 320, category_name: 'Institutional', group_name: 'C' },
        { name: 'Ivy', number_of_shares: 10, category_name: 'Retail', group_name: 'D' },
        { name: 'Jack', number_of_shares: 100, category_name: 'Retail', group_name: 'E' },
        { name: 'Karen', number_of_shares: 170, category_name: 'Retail', group_name: 'F' },
        { name: 'Leo', number_of_shares: 180, category_name: 'Institutional', group_name: 'G' },
      ]);
    } else {
      setInvestorData([]);
    }
  };

  // 同步DataTable初始化与销毁
  useEffect(() => {
    // 先销毁已存在的DataTable实例
    if ($.fn.DataTable.isDataTable('#investorTable')) {
      $('#investorTable').DataTable().destroy(true); // true参数会还原表格到初始状态
    }

    // 数据存在时初始化DataTable
    if (investorData.length > 0) {
      // 使用requestAnimationFrame确保React已完成DOM渲染
      requestAnimationFrame(() => {
        $('#investorTable').DataTable({
          lengthChange: true,
          autoWidth: true,
          scrollCollapse: true,
        });
      });
    }

    // 清理函数:组件卸载时销毁表格
    return () => {
      if ($.fn.DataTable.isDataTable('#investorTable')) {
        $('#investorTable').DataTable().destroy(true);
      }
    };
  }, [investorData]);

  return (
    <Container fluid>
      <Row className="m-1">
        <Col xs={12}>
          <h4 className="main-title">Stock Summary</h4>
        </Col>
      </Row>

      <Row>
        <Col xs={12}>
          <Card>
            <CardHeader>
              <h5>Select Stock Date</h5>
            </CardHeader>
            <CardBody>
              <Form>
                <FormGroup>
                  <Label>Select Date</Label>
                  <Input
                    type="select"
                    name="stockDate"
                    id="stockDate"
                    value={selectedDate}
                    onChange={handleDateChange}
                  >
                    <option value="">-- Select Stock Date --</option>
                    {stockDates.map((date) => (
                      <option key={date.stock_date} value={date.stock_date}>
                        {date.stock_date}
                      </option>
                    ))}
                  </Input>
                </FormGroup>
              </Form>
            </CardBody>
          </Card>
        </Col>
      </Row>

      <Row>
        <Col xs={12}>
          <Card>
            <CardHeader>
              <h5>Investor Data</h5>
            </CardHeader>
            <CardBody>
              <Table id="investorTable" className="w-100 display row-border-table align-middle">
                <thead>
                  <tr>
                    <th>Investor Name</th>
                    <th>Share Count</th>
                    <th>Category</th>
                    <th>Group</th>
                  </tr>
                </thead>
                <tbody>
                  {investorData.map((investor) => (
                    <tr key={`${investor.name}-${investor.group_name}`}>
                      <td>{investor.name}</td>
                      <td>{investor.number_of_shares}</td>
                      <td>{investor.category_name}</td>
                      <td>{investor.group_name}</td>
                    </tr>
                  ))}
                </tbody>
              </Table>
            </CardBody>
          </Card>
        </Col>
      </Row>
    </Container>
  );
};

export default StockSummary;

内容的提问来源于stack exchange,提问作者Chuah Sze Wei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:34:56