@mui/x-data-grid 中的 ValueGetter 函数接收未定义的参数

问题描述 投票:0回答:1

我正在使用 @mui/x-data-grid 版本 7.3.0,并且我的列定义中的 valueGetter 函数存在问题。 valueGetter 函数应该接收的 params 对象未定义。

这是我的代码的简化版本:

const columns = dates.map((date) => ({
  field: date,
  headerName: date,
  width: 150,
  valueGetter: (params) => {
    const { row } = params
    const dateShareValue = row.shares ? row.shares[date] || 0 : 0
    return dateShareValue
  },
}))

<DataGrid rows={rows} columns={columns} pageSize={5} rowsPerPageOptions={[5]} />

行数据的结构如下:

const rows = [
  {
    id: 1,
    memberName: 'John Doe',
    shares: {
      '23/05/2024': 10,
      '22/05/2024': 20,
    },
  },
  // other rows...
]

当我在 valueGetter 函数中记录 params 对象时,它是未定义的。我希望它是一个具有与 DataGrid 中当前行相对应的 row 属性的对象。

我已经检查了 DataGrid 组件是否正确渲染,以及 rows 和 columns 属性是否正确传递。

这是完整的组件代码:

import React, { useEffect, useState } from 'react'
import { collection, getDocs, query, where } from 'firebase/firestore'
import { db } from './firebase' // Import your Firebase app instance
import { BounceLoader } from 'react-spinners'
import { DataGrid } from '@mui/x-data-grid'

const SubmittedData = () => {
  const [loading, setLoading] = useState(true)
  const [submittedData, setSubmittedData] = useState([])
  const [selectedFullName, setSelectedFullName] = useState(null)
  const [meetingsData, setMeetingsData] = useState([])
  const [meetingsLoading, setMeetingsLoading] = useState(false)
  const [error, setError] = useState(null)

  useEffect(() => {
    const fetchData = async () => {
      try {
        const collectorsRef = collection(db, 'collectors')
        const querySnapshot = await getDocs(collectorsRef)
        const data = querySnapshot.docs
          .map((doc) => {
            const docData = doc.data()
            return {
              id: doc.id, // Use Firestore doc id as the key
              solidarityName: docData.solidarityName,
              fullName: docData.fullName,
              address: docData.address,
            }
          })
          .filter((item) => item.fullName && item.solidarityName && item.address) // Guard clause to prevent empty lists
        setSubmittedData(data)
        setLoading(false)
      } catch (error) {
        console.error('Error fetching data:', error)
        setError('Failed to fetch data. Please try again.')
        setLoading(false)
      }
    }

    fetchData()
  }, [])

  const fetchMeetingsData = async (solidarityName) => {
    console.log('fetchMeetingsData called with:', solidarityName) // Log the value of solidarityName
    if (!solidarityName) return // Guard clause to prevent querying with an undefined solidarityName

    setMeetingsLoading(true)
    try {
      const meetingsRef = collection(db, 'meetings')
      const q = query(meetingsRef, where('solidarityName', '==', solidarityName))
      const querySnapshot = await getDocs(q)
      console.log('querySnapshot:', querySnapshot) // Log the querySnapshot
      const data = querySnapshot.docs.flatMap((doc) => {
        const docData = doc.data()
        console.log('doc data:', docData) // Log the data of each document
        return docData.rows.map((row, index) => ({
          id: doc.id, // Assuming each document has a unique id
          number: index + 1, // Add a 'number' field to each object
          memberNames: Array.isArray(row.memberNames) ? row.memberNames : [row.memberNames],
          share: row.share,
          joinDate: row.joinDate ? new Date(row.joinDate).toLocaleDateString() : 'N/A', // Handle invalid joinDate
        }))
      })

      console.log('data:', data) // Log the processed data
      setMeetingsData(data)
      setMeetingsLoading(false)
      console.log('meetingsData:', meetingsData)
    } catch (error) {
      console.error('Error fetching meetings data:', error)
      setError('Failed to fetch meetings data. Please try again.')
      setMeetingsLoading(false)
    }
  }

  const handleCardClick = async (fullName) => {
    console.log('Card clicked:', fullName)
    if (selectedFullName === fullName) {
      // If the same card is clicked, toggle off the table
      setSelectedFullName(null)
      setMeetingsData([])
    } else {
      // Fetch the solidarityName of the selected collector
      const selectedCollector = submittedData.find((collector) => collector.fullName === fullName)
      if (selectedCollector) {
        const { solidarityName } = selectedCollector
        setSelectedFullName(fullName)
        fetchMeetingsData(solidarityName)
      }
    }
  }

  const transformData = (data) => {
    const dates = [...new Set(data.map((item) => item.joinDate))].sort(
      (a, b) => new Date(a) - new Date(b)
    )
    const rows = data.reduce((acc, item) => {
      const member = item.memberNames
      if (!acc[member]) {
        // acc[member] = { id: Object.keys(acc).length + 1, memberName: member, shares: {} }
        acc[member] = { id: Object.keys(acc).length + 1, memberName: member[0], shares: {} }
      }
      if (typeof item.share === 'number') {
        acc[member].shares[item.joinDate] = item.share
      }
      return acc
    }, {})

    console.log('Transformed Data:', { dates, rows: Object.values(rows) }) // Log the transformed data
    return { dates, rows: Object.values(rows) }
  }

  const { dates, rows } = transformData(meetingsData)

  console.log('dates:', dates)
  console.log('rows:', rows)
  const columns = [
    { field: 'id', headerName: 'N°', width: 70 },
    { field: 'memberName', headerName: 'MemberNames', width: 200 },
    ...dates.map((date) => ({
      field: date,
      headerName: date,
      width: 130,

      valueGetter: (params) => {
        console.log('value of date:', date)

        console.log('params:', params)
        if (!params || !params.row) {
          // console.error('params or params.row is undefined:', params)
          return 0
        }
        const { row } = params
        const dateShareValue = row.shares && row.shares[date] ? row.shares[date] : 0
        return dateShareValue
      },
    })),
  ]

  if (loading) {
    return (
      <div
        style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}
      >
        <BounceLoader color="#00BFFF" loading={loading} size={150} />
      </div>
    )
  }

  if (error) {
    return (
      <div
        style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}
      >
        <p>{error}</p>
      </div>
    )
  }

  return (
    <div className="p-6 bg-white shadow-md rounded-lg">
      <h2 className="text-2xl font-semibold pb-3 mb-4 border-b border-gray-300">Submitted Data</h2>
      <div className="flex flex-col gap-6">
        {submittedData.map((data) => (
          <div
            key={data.id}
            onClick={() => handleCardClick(data.fullName)}
            className="cursor-pointer p-6 border border-gray-200 rounded-md shadow-sm bg-gray-50 flex flex-col transition duration-300 ease-in-out transform hover:scale-105 hover:bg-gray-100"
          >
            <h3 className="text-xl font-semibold text-gray-800 mb-2">
              <span className="material-icons mr-2 text-blue-600">Solidarity Group:</span>
              {data.solidarityName}
            </h3>
            <p className="text-gray-700 mb-1 flex items-center">
              <span className="material-icons mr-2 text-green-600">Collector:</span>
              {data.fullName}
            </p>
            <p className="text-gray-700 flex items-center">
              <span className="material-icons mr-2 text-red-600">Address:</span>
              {data.address}
            </p>
          </div>
        ))}
      </div>

      {selectedFullName && (
        <div className="mt-6">
          <h2 className="text-2xl font-semibold pb-3 mb-4 border-b border-gray-300">
            Meetings conducted by:{' '}
            <span className="material-icons mr-2 text-green-600">{selectedFullName}</span>
          </h2>
          {meetingsLoading ? (
            <div
              style={{
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                height: '200px',
              }}
            >
              <BounceLoader color="#00BFFF" loading={meetingsLoading} size={75} />
            </div>
          ) : (
            <div style={{ height: 400, width: '100%' }}>
              <DataGrid rows={rows} columns={columns} pageSize={5} rowsPerPageOptions={[5]} />
            </div>
          )}
        </div>
      )}
    </div>
  )
}

export default SubmittedData

是否有我遗漏的东西,或者这是 DataGrid 组件中的错误?任何帮助将不胜感激。

谢谢你。

我已经记录了transformData函数中的日期和行,它们是正确的。我还在 valueGetter 函数中记录了日期,它也是正确的。我尝试在 valueGetter 函数中渲染给定的字符串,它按预期工作。但是,当我尝试访问 valueGetter 函数中的 params 对象时,它是未定义的。这导致 valueGetter 函数为数据网格中的所有单元格返回 0,而不是为每个日期返回正确的共享值。

javascript reactjs google-cloud-firestore material-ui datagrid
1个回答
0
投票

仅通过阅读您的代码,看起来

valueGetter
组件中的
@mui/x-data-grid
函数存在问题,其中
params
对象未定义。这可能是由于
rows
数据的构造方式或传递到
DataGrid
的方式造成的。以下是一些可能的解决方案和需要检查的事项:

那么你应该:

  1. 确保数据已完全加载:确保
    DataGrid
    仅在数据完全加载后呈现。
  2. 验证数据结构:确保
    rows
    数据具有预期的结构并包含必要的字段。

这里是更正后的代码,带有额外的错误处理和验证,以确保正确处理

params
对象:

import React, { useEffect, useState } from 'react';
import { collection, getDocs, query, where } from 'firebase/firestore';
import { DataGrid } from '@mui/x-data-grid';
import { db } from './firebase'; // Adjust the import path as needed

const SubmittedData = () => {
  const [loading, setLoading] = useState(true);
  const [submittedData, setSubmittedData] = useState([]);
  const [meetingsData, setMeetingsData] = useState([]);
  const [selectedFullName, setSelectedFullName] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const collectorsRef = collection(db, 'collectors');
        const querySnapshot = await getDocs(collectorsRef);
        const data = querySnapshot.docs.map((doc) => {
          const docData = doc.data();
          return {
            id: doc.id,
            solidarityName: docData.solidarityName,
            fullName: docData.fullName,
            address: docData.address,
          };
        }).filter((item) => item.fullName && item.solidarityName && item.address);
        setSubmittedData(data);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching data:', error);
        setError('Failed to fetch data. Please try again.');
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  const fetchMeetingsData = async (solidarityName) => {
    if (!solidarityName) return;

    try {
      const meetingsRef = collection(db, 'meetings');
      const q = query(meetingsRef, where('solidarityName', '==', solidarityName));
      const querySnapshot = await getDocs(q);
      const data = querySnapshot.docs.flatMap((doc) => {
        const docData = doc.data();
        return docData.rows.map((row, index) => ({
          id: `${doc.id}-${index}`,
          number: index + 1,
          memberNames: Array.isArray(row.memberNames) ? row.memberNames : [row.memberNames],
          share: row.share,
          joinDate: row.joinDate ? new Date(row.joinDate).toLocaleDateString() : 'N/A',
        }));
      });

      setMeetingsData(data);
    } catch (error) {
      console.error('Error fetching meetings data:', error);
      setError('Failed to fetch meetings data. Please try again.');
    }
  };

  const handleCardClick = async (fullName) => {
    if (selectedFullName === fullName) {
      setSelectedFullName(null);
      setMeetingsData([]);
    } else {
      const selectedCollector = submittedData.find((collector) => collector.fullName === fullName);
      if (selectedCollector) {
        const { solidarityName } = selectedCollector;
        setSelectedFullName(fullName);
        fetchMeetingsData(solidarityName);
      }
    }
  };

  const transformData = (data) => {
    const dates = [...new Set(data.map((item) => item.joinDate))].sort(
      (a, b) => new Date(a) - new Date(b)
    );
    const rows = data.reduce((acc, item) => {
      const member = item.memberNames.join(', ');
      if (!acc[member]) {
        acc[member] = { id: Object.keys(acc).length + 1, memberName: member, shares: {} };
      }
      if (typeof item.share === 'number') {
        acc[member].shares[item.joinDate] = item.share;
      }
      return acc;
    }, {});

    return { dates, rows: Object.values(rows) };
  };

  const { dates, rows } = transformData(meetingsData);

  const columns = [
    { field: 'id', headerName: 'N°', width: 70 },
    { field: 'memberName', headerName: 'MemberNames', width: 200 },
    ...dates.map((date) => ({
      field: date,
      headerName: date,
      width: 130,
      valueGetter: (params) => {
        if (!params || !params.row) {
          console.error('params or params.row is undefined:', params);
          return 0;
        }
        const { row } = params;
        const dateShareValue = row.shares && row.shares[date] ? row.shares[date] : 0;
        return dateShareValue;
      },
    })),
  ];

  if (loading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>{error}</div>;
  }

  return (
    <div>
      <div>
        {submittedData.map((data) => (
          <div key={data.id} onClick={() => handleCardClick(data.fullName)}>
            <h3>{data.solidarityName}</h3>
            <p>{data.fullName}</p>
            <p>{data.address}</p>
          </div>
        ))}
      </div>

      {selectedFullName && (
        <div>
          <h2>Meetings conducted by: {selectedFullName}</h2>
          <div style={{ height: 400, width: '100%' }}>
            <DataGrid rows={rows} columns={columns} pageSize={5} rowsPerPageOptions={[5]} />
          </div>
        </div>
      )}
    </div>
  );
};

export default SubmittedData;

这是我更新的答案:

为了确保

params
函数中的
valueGetter
对象正确定义,您需要:

  1. 确保数据加载:确保
    DataGrid
    仅在数据完全加载后呈现。
  2. 验证数据结构: 确保
    rows
    dates
    的结构正确。
  3. 跟踪
    valueGetter
    添加控制台日志以跟踪
    params
    对象。
  4. 优雅处理:
    params
    函数中优雅地处理未定义的
    valueGetter

这是经过额外调试的更新后的组件:

import React, { useEffect, useState } from 'react';
import { collection, getDocs, query, where } from 'firebase/firestore';
import { BounceLoader } from 'react-spinners';
import { DataGrid } from '@mui/x-data-grid';
import { db } from './firebase';

const SubmittedData = () => {
  const [loading, setLoading] = useState(true);
  const [submittedData, setSubmittedData] = useState([]);
  const [meetingsData, setMeetingsData] = useState([]);
  const [selectedFullName, setSelectedFullName] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const collectorsRef = collection(db, 'collectors');
        const querySnapshot = await getDocs(collectorsRef);
        const data = querySnapshot.docs.map((doc) => {
          const docData = doc.data();
          return {
            id: doc.id,
            solidarityName: docData.solidarityName,
            fullName: docData.fullName,
            address: docData.address,
          };
        }).filter((item) => item.fullName && item.solidarityName && item.address);
        setSubmittedData(data);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching data:', error);
        setError('Failed to fetch data. Please try again.');
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  const fetchMeetingsData = async (solidarityName) => {
    if (!solidarityName) return;

    try {
      const meetingsRef = collection(db, 'meetings');
      const q = query(meetingsRef, where('solidarityName', '==', solidarityName));
      const querySnapshot = await getDocs(q);
      const data = querySnapshot.docs.flatMap((doc) => {
        const docData = doc.data();
        return docData.rows.map((row, index) => ({
          id: `${doc.id}-${index}`,
          number: index + 1,
          memberNames: Array.isArray(row.memberNames) ? row.memberNames : [row.memberNames],
          share: row.share,
          joinDate: row.joinDate ? new Date(row.joinDate).toLocaleDateString() : 'N/A',
        }));
      });

      setMeetingsData(data);
    } catch (error) {
      console.error('Error fetching meetings data:', error);
      setError('Failed to fetch meetings data. Please try again.');
    }
  };

  const handleCardClick = async (fullName) => {
    if (selectedFullName === fullName) {
      setSelectedFullName(null);
      setMeetingsData([]);
    } else {
      const selectedCollector = submittedData.find((collector) => collector.fullName === fullName);
      if (selectedCollector) {
        const { solidarityName } = selectedCollector;
        setSelectedFullName(fullName);
        fetchMeetingsData(solidarityName);
      }
    }
  };

  const transformData = (data) => {
    const dates = [...new Set(data.map((item) => item.joinDate))].sort(
      (a, b) => new Date(a) - new Date(b)
    );
    const rows = data.reduce((acc, item) => {
      const member = item.memberNames.join(', ');
      if (!acc[member]) {
        acc[member] = { id: Object.keys(acc).length + 1, memberName: member, shares: {} };
      }
      if (typeof item.share === 'number') {
        acc[member].shares[item.joinDate] = item.share;
      }
      return acc;
    }, {});

    return { dates, rows: Object.values(rows) };
  };

  const { dates, rows } = transformData(meetingsData);

  console.log('Dates:', dates); // Log dates to ensure they are correct
  console.log('Rows:', rows); // Log rows to ensure they are correct

  const columns = [
    { field: 'id', headerName: 'N°', width: 70 },
    { field: 'memberName', headerName: 'MemberNames', width: 200 },
    ...dates.map((date) => ({
      field: date,
      headerName: date,
      width: 130,
      valueGetter: (params) => {
        console.log('params:', params); // Log params to trace the issue

        if (!params || !params.row) {
          console.error('params or params.row is undefined:', params);
          return 0;
        }
        const { row } = params;
        const dateShareValue = row.shares && row.shares[date] ? row.shares[date] : 0;
        return dateShareValue;
      },
    })),
  ];

  if (loading) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
        <BounceLoader color="#00BFFF" loading={loading} size={150} />
      </div>
    );
  }

  if (error) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
        <p>{error}</p>
      </div>
    );
  }

  return (
    <div className="p-6 bg-white shadow-md rounded-lg">
      <h2 className="text-2xl font-semibold pb-3 mb-4 border-b border-gray-300">Submitted Data</h2>
      <div className="flex flex-col gap-6">
        {submittedData.map((data) => (
          <div
            key={data.id}
            onClick={() => handleCardClick(data.fullName)}
            className="cursor-pointer p-6 border border-gray-200 rounded-md shadow-sm bg-gray-50 flex flex-col transition duration-300 ease-in-out transform hover:scale-105 hover:bg-gray-100"
          >
            <h3 className="text-xl font-semibold text-gray-800 mb-2">
              <span className="material-icons mr-2 text-blue-600">Solidarity Group:</span>
              {data.solidarityName}
            </h3>
            <p className="text-gray-700 mb-1 flex items-center">
              <span className="material-icons mr-2 text-green-600">Collector:</span>
              {data.fullName}
            </p>
            <p className="text-gray-700 flex items-center">
              <span className="material-icons mr-2 text-red-600">Address:</span>
              {data.address}
            </p>
          </div>
        ))}
      </div>

      {selectedFullName && (
        <div className="mt-6">
          <h2 className="text-2xl font-semibold pb-3 mb-4 border-b border-gray-300">
            Meetings conducted by: <span className="material-icons mr-2 text-green-600">{selectedFullName}</span>
          </h2>
          <div style={{ height: 400, width: '100%' }}>
            <DataGrid rows={rows} columns={columns} pageSize={5} rowsPerPageOptions={[5]} />
          </div>
        </div>
      )}
    </div>
  );
};

export default SubmittedData;
© www.soinside.com 2019 - 2024. All rights reserved.