How to map my firestore collection documents into a table - reactjs

So I have a collection in firebase and want all the documents populated in the table dynamically. At the moment, it just populates the last document onto the table even after using the spread operator. Somebody help me with a solution to this. Thank you.
Here is the code:
import React, { useEffect, useState } from "react";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../../firebase/firebase";
import { DataGrid } from "#mui/x-data-grid";
const columns = [
{ field: "name", headerName: "Name", width: 160 },
{ field: "email", headerName: "Email", width: 210 },
{ field: "roles", headerName: "Roles", width: 160 },
{ field: "isSuspended", headerName: "Suspended", width: 130 },
{ field: "lastUpdated", headerName: "Last Updated", width: 150 },
{ field: "updatedByEmail", headerName: "Updated By", width: 150 },
];
export default function Admins() {
const [row, setRow] = useState([]);
useEffect(() => {
const getAdmins = async () => {
const admins = await getDocs(collection(db, "admins"));
admins.forEach((admin) => {
console.log(admin.data());
setRow([
...row,
{
id: admin.data().email,
name: admin.data().name,
email: admin.data().email,
roles: admin.data().roles,
isSuspended: admin.data().isSuspended,
lastUpdated: admin.data().lastUpdated,
updatedByEmail: admin.data().updatedByEmail,
},
]);
});
};
getAdmins();
}, []);
console.log("row", row);
return (
<div style={{ height: 400, width: "100%" }}>
<DataGrid
rows={row}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
checkboxSelection
/>
</div>
);
}

The reason this is happening is because the state doesn't update completely until the use effect is done. This means the state is the same as it was before the use effect ran. Here's how you can fix this:
Instead of using
setRow([
...row,
{
id: admin.data().email,
name: admin.data().name,
email: admin.data().email,
roles: admin.data().roles,
isSuspended: admin.data().isSuspended,
lastUpdated: admin.data().lastUpdated,
updatedByEmail: admin.data().updatedByEmail,
},
]);
Use
setRow((r) => ([
...r,
{
id: admin.data().email,
name: admin.data().name,
email: admin.data().email,
roles: admin.data().roles,
isSuspended: admin.data().isSuspended,
lastUpdated: admin.data().lastUpdated,
updatedByEmail: admin.data().updatedByEmail,
},
]));
By passing a function with the r argument, react knows to pass the actual current state value to the function, which will just return something, and that will be put into state.

Related

getRowId from Material UI datagrid doesn't work

I keep getting this error Uncaught Error: MUI: The data grid component requires all rows to have a unique id property. Even though I have passed the getRowId prop into the datagrid and defined what the Id should be. I am fetching this data from a firestore. What might I be doing wrong? Here is the component code:
import React, { useEffect, useState } from "react";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../../firebase/firebase";
import { DataGrid } from "#mui/x-data-grid";
const columns = [
{ field: "name", headerName: "Name", width: 160 },
{ field: "email", headerName: "Email", width: 210 },
{ field: "roles", headerName: "Roles", width: 160 },
{ field: "isSuspended", headerName: "Suspended", width: 130 },
{ field: "lastUpdated", headerName: "Last Updated", width: 150 },
{ field: "updatedByEmail", headerName: "Updated By", width: 150 },
];
export default function Admins() {
const [rows, setRows] = useState([]);
useEffect(() => {
const getAdmins = async () => {
const admins = await getDocs(collection(db, "admins"));
admins.forEach((admin) => {
setRows((row) => [
...row,
{
id: admin.data().email,
name: admin.data().name,
email: admin.data().email,
roles: admin.data().roles,
isSuspended: admin.data().isSuspended,
lastUpdated: admin.data().lastUpdated,
updatedByEmail: admin.data().updatedByEmail,
},
]);
});
};
getAdmins();
}, []);
console.log("rows", rows);
return (
<div style={{ height: "100vh", width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
getRowId={(row) => row.email}
pageSize={10}
rowsPerPageOptions={[10]}
checkboxSelection
/>
</div>
);
}
One suggestion is to first check if you are getting the value for the row.email in case is returned as undefined and try using optional chaining getRowId={(row) => row?.email}

Data is not showing in Grid Lookup React.js MaterialUI

I need to show my lookup like this
Lookup image what I want
So the code for this lookup is which is shown is hardcoded but i want to do retrieve data dynamically.
Hardcoded data
I had done with to retrieve data from database and the data is also in same format when i console log the both they are same.
AvaibleMechanic is which we retrieve from DB and dyanmicMechanicLookUp is harcoded
Both data console log SS
setColumnsCode
const [columns, setColumns] = useState([
{ title: "OrderId", field: "_id", editable: "never" },
{ title: "Customer Name", field: "customerName", editable: "never" },
{ title: "Car Name", field: "carName", editable: "never" },
{ title: "Car Number", field: "carNumber", editable: "never" },
{ title: "Address", field: "custAddress", editable: "never" },
{ title: "Service Name", field: "serviceName", editable: "never" },
{ title: "Price", field: "servicePrice", editable: "never" },
{
title: "Assign Mechanic",
field: "mechanicId",
lookup: AvailableMechanic,
},
]);
when i want to see data which is retrieve from Database is not show on the lookup is look like this
Retrieve from database lookup SS
You can see when I used dynamicLookUp which is hardcoded it works fine but when i used data which is retrieve it's not shown on the grid
FullCode
import React, { useState, useEffect } from "react";
import AdminOrders from "../../../services/member/orders.js/admin_orders";
import MechanicService from "../../../services/member/Mechanic/Mechanic_Services";
import "./CSS/Cars.css";
import MaterialTable from "material-table";
import { useSnackbar } from "notistack";
function Orders() {
const [orders, setOrders] = useState([]);
const [completedOrders, setCompletedOrders] = useState([]);
const [AvailableMechanic, setAvailableMechanic] = useState({});
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
//for error handling
const [iserror, setIserror] = useState(false);
const [errorMessages, setErrorMessages] = useState([]);
const getAvailableMechanics = async () => {
await MechanicService.findAvailable()
.then((res) => {
res.map((key,i) => {
setAvailableMechanic(prevState => ({
...prevState,
[key._id]: key.name
}))
})
})
.catch((err) => {
console.log(err)
})
}
const getPlacedOrders = () => {
AdminOrders.findPlacedOrders()
.then((response) => {
setOrders(response);
})
.catch((err) => {
console.log(err);
});
};
const getCompletedOrders = () => {
AdminOrders.findCompletedOrders()
.then((res) => {
setCompletedOrders(res);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getAvailableMechanics();
getPlacedOrders();
getCompletedOrders();
}, []);
const dynamicMechanicsLookUp = {
'621d06355e364ae391f59af4': "John Doe 1",
'621e3fb9d99ae5efd754b5d6': "John Doe 2",
'62766409db9ad573da5655cc': "John Doe 3",
'6276642cdb9ad573da5655d1': "John Doe 4",
'62766433db9ad573da5655d4': "John Doe 5",
};
// const dynamicMechanicsLookUp = AvailableMechanic;
console.log("dynamicLookUp",dynamicMechanicsLookUp)
console.log("AvailableMechanic",AvailableMechanic)
const [columns, setColumns] = useState([
{ title: "OrderId", field: "_id", editable: "never" },
{ title: "Customer Name", field: "customerName", editable: "never" },
{ title: "Car Name", field: "carName", editable: "never" },
{ title: "Car Number", field: "carNumber", editable: "never" },
{ title: "Address", field: "custAddress", editable: "never" },
{ title: "Service Name", field: "serviceName", editable: "never" },
{ title: "Price", field: "servicePrice", editable: "never" },
{
title: "Assign Mechanic",
field: "mechanicId",
lookup: AvailableMechanic,
},
]);
const [column, setColumn] = useState([
{ title: "OrderId", field: "_id" },
{ title: "Customer Name", field: "customerName" },
{ title: "Car Name", field: "carName" },
{ title: "Car Number", field: "carNumber" },
{ title: "Address", field: "custAddress" },
{ title: "Service Name", field: "serviceName" },
{ title: "Price", field: "servicePrice" },
{ title: "Assigned Mechanic", field: "mechanicId" },
]);
const handleRowUpdate = (newData, oldData, resolve) => {
let errorList = [];
if (errorList.length < 1) {
AdminOrders.assignOrder(newData._id, newData.mechanicId)
.then((res) => {
const dataUpdate = [...orders];
const index = oldData.tableData.id;
dataUpdate[index] = newData;
setOrders([...dataUpdate]);
resolve();
setIserror(false);
setErrorMessages([]);
enqueueSnackbar(res, {
variant: "success",
});
})
.catch((error) => {
setErrorMessages(["Update failed! Server error"]);
setIserror(true);
resolve();
});
} else {
setErrorMessages(errorList);
setIserror(true);
resolve();
}
};
const [display, setdisplay] = useState(false);
const openTable = () => {
setdisplay(true);
};
const closeTable = () => {
setdisplay(false);
};
return (
<div className="cars_container">
<br />
<button onClick={openTable}>See Completed Orders</button>
<br />
{orders ? (
<MaterialTable
title="CURRENT ORDERS DATA"
columns={columns}
data={orders}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
handleRowUpdate(newData, oldData, resolve);
}),
}}
options={{
headerStyle: {
backgroundColor: "#01579b",
color: "#FFF",
},
exportButton: true,
}}
/>
) : (
<div>
<br />
<h2>NO CURRENT ORDERS RIGHT NOW</h2>
</div>
)}
<br />
<br />
<br />
{display ? (
<div>
<h1>COMPLETED ORDERS</h1>
<MaterialTable
title="CURRENT ORDERS DATA"
columns={column}
data={completedOrders}
options={{
headerStyle: {
backgroundColor: "#01579b",
color: "#FFF",
},
exportButton: true,
}}
/>
<br />
<button onClick={closeTable}>Close Table</button>
<br />
<br />
<br />
</div>
) : null}
</div>
);
}
export default Orders;
Sorry for the bad english. Please if you find any mistake i am making please let me know
Thanks

Adding colDefs dynamically

I'm trying to add the column definition programmatically,on button click, instead of hardcoding it in my ReactJS page.
{
headerName: "Product1",
resizable: true,
wrapText: true,
cellStyle: {
'white-space': 'normal'
},
autoHeight: true,
hide: true,
cellRendererFramework.MyCustomColumnRenderer
}
Not sure how to go about implementing this?
Thanks for your help.
Use setColumnDefs(columnDefs)
const columnDefs = getColumnDefs();
columnDefs.forEach(function (colDef, index) {
colDef.headerName = 'Abcd';
});
this.gridApi.setColumnDefs(columnDefs);
https://plnkr.co/edit/0ctig4P2yzPjhycB
You could define the columnDefs in the grid to use a state and then set the state dynamically.
import React from 'react';
import { render } from 'react-dom';
import { AgGridColumn, AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
const App = () => {
const rowData = [
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxter', price: 72000 },
];
// use the colDefs state to define the column definitions
const [colDefs, setColDefs] = React.useState([{ field: 'make' }]);
// when the button is pressed set the state to cause the grid to update
const handleAddColumns = ()=> {
const dynamicFields = [
{ field: 'make', header: 'Car Make' },
{ field: 'model', sortable: true },
{ field: 'price' },
];
setColDefs(dynamicFields);
}
return (
<div>
<button onClick={handleAddColumns}>Add Column Defs</button>
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
rowData={rowData}
columnDefs={colDefs}>
</AgGridReact>
</div>
</div>
);
};
render(<App />, document.getElementById('root'));
Another approach is to use the Api's setColumnDef method:
import React from 'react';
import { render } from 'react-dom';
import { AgGridColumn, AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
const App = () => {
const rowData = [
{ make: 'Toyota', model: 'Celica', price: 35000 },
{ make: 'Ford', model: 'Mondeo', price: 32000 },
{ make: 'Porsche', model: 'Boxter', price: 72000 },
];
// using state to define the columns initially
const [colDefs, setColDefs] = React.useState([{ field: 'make' }]);
// get a reference to the API when the onGridReady is fired
// see the Grid definition in the JSX
const [gridApi, setGridApi] = React.useState([]);
const handleAddColumns = ()=> {
const dynamicFields = [
{ field: 'make', header: 'Car Make' },
{ field: 'model', sortable: true },
{ field: 'price' },
];
// use the API to set the Column Defs
gridApi.setColumnDefs(dynamicFields);
}
return (
<div>
<button onClick={handleAddColumns}>Add Column Defs</button>
<div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
<AgGridReact
rowData={rowData}
columnDefs={colDefs}
onGridReady={ params => {setGridApi(params.api)} }
></AgGridReact>
</div>
</div>
);
};
render(<App />, document.getElementById('root'));
The examples in the documentation should help:
https://www.ag-grid.com/react-data-grid/column-definitions/
https://www.ag-grid.com/react-data-grid/column-updating-definitions/
There is also a blog post from AG Grid that covers dynamic column definitions:
https://blog.ag-grid.com/binding-and-updating-column-definitions-in-ag-grid/

Ant table custom filter checkbox without dropdown

I am using ant table for my project where I want to filter records on click of checkbox inside my header row, when I click on check box all zero valued rows should be filtered and others should stay, is there any way I can do this?
Demo
You can achieve the desired feature by defining a custom columns title prop that renders a controlled Checkbox component in addition to the column's title string. When the Checkbox is true, you then filter out the table data based on your desired filter condition.
(As an aside, I did initially try to get the same functionality to work via the onFilter and filterIcon approach, but that approach proved unsuccessful.)
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Table, Checkbox } from "antd";
import "./index.scss";
const DifferenceTable = (props) => {
const [isChecked, setIsChecked] = useState(false);
const data = [
{
date: "2020-06-17",
units: 2353.0,
amount: 8891206.27,
date: 2323,
units: 243234,
amount: 234234,
units_diff: 0,
amount_diff: 0
},
{
date: "2020-06-17",
units: 2353.0,
amount: 8891206.27,
date: 2323,
units: 243234,
amount: 234234,
units_diff: 1,
amount_diff: 1
}
];
const processedData = isChecked
? data.filter((datum) => datum.units_diff || datum.amount_diff)
: data;
const columns = [
{
title: "Bank",
children: [
{
title: "Trxn Date",
dataIndex: "date",
key: "date",
width: 100
},
{
title: "Sum Units",
dataIndex: "units",
key: "units",
width: 100
},
{
title: "Sum Amounts",
dataIndex: "amount",
key: "units",
width: 100
}
]
},
{
title: "CUSTOMER",
children: [
{
title: "Trxn Date",
dataIndex: "date",
key: "date",
width: 100
},
{
title: "Sum Units",
dataIndex: "units",
key: "units",
width: 100
},
{
title: "Sum Amounts",
dataIndex: "amount",
key: "amount",
width: 100
}
]
},
{
title: () => (
<div>
<span>Difference </span>
<Checkbox
checked={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
}}
/>
</div>
),
dataIndex: "units_diff",
key: "units_diff",
children: [
{
title: "Units",
dataIndex: "units_diff",
key: "units_diff",
width: 100
},
{
title: "Amounts",
dataIndex: "amount_diff",
key: "amount_diff",
width: 100
}
],
align: "center"
}
];
return (
<Table
// rowKey="uid"
className="table diff_table"
columns={columns}
dataSource={processedData}
pagination={false}
scroll={{ y: 400, x: 0 }}
/>
);
};
ReactDOM.render(<DifferenceTable />, document.getElementById("container"));
A functional demo is available at the following CodeSandbox link

REACT / AG-GRID: Dynamically setting columnDefs after retrieving data

Within the componentDidMount() function, I'm using AXIOS to retrieve data and once received, I'm trying to change the column Header Names of my AG-GRID after retrieving data, but the Header Names are unaffected.
Please see line this.gridOptions.api.setColumnDefs(columnDefs) in the following code.
var columnDefs = [
{ headerName: "column0", field: "column0", width: 300 },
{ headerName: "column1", field: "column1", width: 100 },
{ headerName: "column2", field: "column2", width: 100 },
{ headerName: "column3", field: "column3", width: 100 },
{ headerName: "column4", field: "column4", width: 100 },
{ headerName: "column5", field: "column5", width: 100 },
];
var PARMS = '';
class Home extends React.Component {
state = {
columnDefs: columnDefs,
header: {},
isLoading: false,
error: null
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(API + PARMS)
.then(fubar => {
const rowData = fubar.data.results;
this.setState({ rowData });
const headerRow = fubar.data.header;
columnDefs[0].headerName = headerRow.column0;
columnDefs[1].headerName = headerRow.column1;
columnDefs[2].headerName = headerRow.column2;
columnDefs[3].headerName = headerRow.column3;
columnDefs[4].headerName = headerRow.column4;
columnDefs[5].headerName = headerRow.column5;
this.gridOptions.api.setColumnDefs(columnDefs);
})
.catch(error => this.setState({
error,
isLoading: false
}));
}
The RENDER() is:
render() {
const { isLoading, rowData, columnDefs } = this.state;
return (
<div className="ag-theme-balham" style={{ height: '525px', width: '920px' }} >
<h2>{heading}</h2>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}>
</AgGridReact>
</div>
);
}
I think what the code above is doing (or trying to do):
Column definitions are defined
Grid is rendered from Column definitions
Data is sourced
Column definitions redefined
Grid is (or should) rerender
But it isn't happening. In my perfect world, I'd instead like to:
Retrieve Data
Define the columns
Render the Grid
But I'm told "it doesn't work that way".
My solution is two define to Arrays with one set up as a STATE object and the other as a stand alone variable. When the data is refreshed, the stand alone variable is update, and is then used to replace the STATE object.
Is there a better way?
var columnDefsNew = [
{ headerName: "", field: "column0", width: 300, },
{ headerName: "", field: "column1", width: 100 },
{ headerName: "", field: "column2", width: 100 },
{ headerName: "", field: "column3", width: 100 },
{ headerName: "", field: "column4", width: 100 },
{ headerName: "", field: "column5", width: 100, }];
class Home extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{ headerName: "", field: "column0", width: 300 },
{ headerName: "", field: "column1", width: 100 },
{ headerName: "", field: "column2", width: 100 },
{ headerName: "", field: "column3", width: 100 },
{ headerName: "", field: "column4", width: 100 },
{ headerName: "", field: "column5", width: 100 }],
rowData: null,
isLoading: false,
error: null
};
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(API + PARMS)
.then(fubar => {
const headerRow = fubar.data.header;
const rowData = fubar.data.results;
this.setState({ rowData });
columnDefsNew[0].headerName = headerRow.column0;
columnDefsNew[1].headerName = headerRow.column1;
columnDefsNew[2].headerName = headerRow.column2;
columnDefsNew[3].headerName = headerRow.column3;
columnDefsNew[4].headerName = headerRow.column4;
columnDefsNew[5].headerName = headerRow.column5;
this.setState({ columnDefs: columnDefsNew });
})
.catch(error => this.setState({
error,
isLoading: false
}));
}
render() {
const { isLoading, rowData, columnDefs } = this.state;
return (
<div className="ag-theme-balham" style={{ height: '525px', width: '900px' }} >
<h2>{heading}</h2>
<AgGridReact
columnDefs={columnDefs}
rowData={rowData}>
</AgGridReact>
</div>
);
}
}
export default Home;
We can able to set dynamic column after retrieve api data by using below function
Here I have used getColumnDefs() and setColumnDefs() from gridApi
const dynamicallyConfigureColumnsFromObject = (anObject, ticketGridRef) => {
const colDefs = ticketGridRef.current.api.getColumnDefs()
const keys = Object.keys(anObject)
keys.forEach(key => {
if (colDefs.some(l => l.field === key) === false) {
colDefs.push({ field: key, filter: 'agTextColumnFilter', headerName: key})
}
})
ticketGridRef.current.api.setColumnDefs(colDefs)
}

Resources