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

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)
}

Related

How to map my firestore collection documents into a table

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.

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}

Get all rows in Material-UI DataGrid component

I am trying to get the data from a DataGrid component using this function, but whe I try to use my useApiRef() function a get the following error from typescript:
Cannot redeclare block scoped variable
Columns:
this is my DataGrid component columns
const columns: GridColumns = [
{
field: "dataVencimento",
headerName: "Vencimento",
type: "date",
editable: true,
},
{ field: "valor", headerName: "Valor", type: "number", editable: true },
{
field: "formaPagamento",
headerName: "Forma de pagamento",
editable: true,
width: 200,
},
];
Function code:
I implemented this function to get data from the DataGrid component
function useApiRef() {
var apiRef = useRef(null);
var _columns: any = useMemo(
() =>
columns.concat({
field: "__HIDDEN__",
width: 0,
renderCell: (params: any) => {
apiRef.current = params.api;
return null;
},
}),
[columns]
);
return { apiRef, columns: _columns };
}
Usage:
I am trying to log my DataGrid data in the console
const { apiRef, columns } = useApiRef();
const handleClickButton = () => {
console.log(apiRef?.current.getRowModels());
};
DataGrid component:
<DataGrid rows={parcelaRows} columns={columns} />

I need to render a JSX element in Material-ui DataGrid

I need to render a styled div conditionally by a given string stored in Object.
I pass the data object throw props and convert it by this function convertOrdersRows.
but it gives me that error TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property '_context' -> object with constructor 'Object'
const orderColumns = [
{ field: "id", headerName: "ID", width: 120 },
{ field: "date", headerName: "Date", width: 140 },
{ field: "customerName", headerName: "Customer Name", width: 190 },
{ field: "products", headerName: "Products", width: 150 },
{ field: "price", headerName: "Price", width: 90 },
{ field: "status", headerName: "Status", width: 120 },
];
const productColumns = [];
const convertToNormalDate = (date) => {
const newDate = new Date(date);
const year = newDate.getFullYear();
let month = newDate.getMonth() + 1;
let dt = newDate.getDate();
if (dt < 10) {
dt = "0" + dt;
}
if (month < 10) {
month = "0" + month;
}
return `${dt}/${month}/${year}`;
};
const convertStatusToIcon = (status) => {
let statusIcon;
switch (status) {
case "Canceled":
return (statusIcon = <Status label="Canceled" canceled />);
case "Pending":
return (statusIcon = <Status label="Pending" pending />);
case "Deliverd":
return (statusIcon = <Status label="Deliverd" deliverd />);
default:
status = <Status label="..." />;
}
return statusIcon;
};
const convertOrdersRows = (rows) => {
let data = [...rows];
return data.map((value) => {
let convertedRow = { ...value };
convertedRow.date = convertToNormalDate(convertedRow.date);
convertedRow.status = convertStatusToIcon(convertedRow.status);
convertedRow.price = `$ ${convertedRow.price}`;
return convertedRow;
});
};
const DataGrid = (props) => {
const classes = useStyles();
const { orders, products, data } = props;
const columns = orders ? orderColumns : products ? productColumns : [];
const rows = convertOrdersRows(data);
console.log(rows);
return (
<MuiDataGrid
rows={rows}
columns={columns}
checkboxSelection
autoPageSize
{...props}
/>
);
};
export default DataGrid;
Remove convertOrdersRows and use the renderCell prop instead as it has access to all cells throw params in the column so you can put any logic you want.
const orderColumns = [
{ field: "id", headerName: "ID", width: 120 },
{
field: "date",
headerName: "Date",
width: 140,
renderCell: (params) => convertToNormalDate(params.value),
},
{ field: "customerName", headerName: "Customer Name", width: 190 },
{ field: "products", headerName: "Products", width: 150 },
{
field: "price",
headerName: "Price",
width: 100,
renderCell: (params) => `$ ${params.value}`,
},
{
field: "status",
headerName: "Status",
width: 120,
renderCell: (params) => convertStatusToIcon(params.value),
},
];

Select rows of the grid problematically

I have a ag-grid has around 100000 rows in it. User can select/deselect multiple rows the grid by using ctrl + click. Selection should work like this -
When user select any row which has its id(one of the columns of the grid) equal to X then grid should automatically select all the rows which has its id equal to X.
Is there any way we can code this behavior?
Thanks in advance.
sample code :
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { AgGridReact } from "ag-grid-react";
import "ag-grid/dist/styles/ag-grid.css";
import "ag-grid/dist/styles/ag-theme-balham.css";
import "./styles.css";
import { LargeTextCellEditor } from "ag-grid";
class GridExample extends Component {
constructor(props) {
super(props);
this.isBusy = false;
this.state = {
columnDefs: [
{
headerName: "Athlete",
field: "athlete",
width: 150,
suppressSizeToFit: true
},
{
headerName: "Age",
field: "age",
width: 90,
minWidth: 50,
maxWidth: 100
},
{
headerName: "Country",
field: "country",
width: 120
},
{
headerName: "Year",
field: "year",
width: 90
},
{
headerName: "Date",
field: "date",
width: 110
},
{
headerName: "Sport",
field: "sport",
width: 110
},
{
headerName: "Gold",
field: "gold",
width: 100
},
{
headerName: "Silver",
field: "silver",
width: 100
},
{
headerName: "Bronze",
field: "bronze",
width: 100
},
{
headerName: "Total",
field: "total",
width: 100
}
],
rowData: []
};
}
_fetchData(cb) {
const httpRequest = new XMLHttpRequest();
const updateData = data => {
// this.setState({ rowData: data });
cb(data);
};
httpRequest.open(
"GET",
"https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/olympicWinnersSmall.json"
);
httpRequest.send();
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
updateData(JSON.parse(httpRequest.responseText));
}
};
}
onGridReady(params) {
//console.log(params);
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
var that = this;
params.api.setDatasource({
getRows(params) {
//console.log("getRows", params);
that._fetchData(data => params.successCallback(data));
}
});
}
//------------------------------------------------
// you need this section
onRowClicked(e) {
this.gridApi.forEachNode(function(node) {
console.log(node.data.age);
node.setSelected(node.data.age === e.data.age);
});
}
render() {
return (
<div style={{ width: "100%", height: "100%" }}>
<div class="grid-wrapper">
<div
id="myGrid"
style={{
boxSizing: "border-box",
height: "100%",
width: "100%"
}}
className="ag-theme-balham"
>
<AgGridReact
rowModelType="infinite"
columnDefs={this.state.columnDefs}
enableColResize={true}
onGridReady={this.onGridReady.bind(this)}
rowData={this.state.rowData}
onRowClicked={this.onRowClicked.bind(this)}
rowSelection={"multiple"}
/>
</div>
</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{ headerName: "Make", field: "make" },
{ headerName: "Model", field: "model" },
{ headerName: "Price", field: "price" }
],
rowData: [
{ make: "Toyota", model: "Celica", price: 35000 },
{ make: "Ford", model: "Mondeo", price: 32000 },
{ make: "Porsche", model: "Boxter", price: 72000 }
]
};
}
render() {
return (
<div
className="ag-theme-balham"
style={{
height: "500px",
width: "600px"
}}
>
<GridExample />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Resources