我正在使用 @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,而不是为每个日期返回正确的共享值。
仅通过阅读您的代码,看起来
valueGetter
组件中的 @mui/x-data-grid
函数存在问题,其中 params
对象未定义。这可能是由于 rows
数据的构造方式或传递到 DataGrid
的方式造成的。以下是一些可能的解决方案和需要检查的事项:
那么你应该:
DataGrid
仅在数据完全加载后呈现。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
对象正确定义,您需要:
DataGrid
仅在数据完全加载后呈现。rows
和 dates
的结构正确。valueGetter
: 添加控制台日志以跟踪 params
对象。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;