What am I doing wrong with the ag-grid in react? - reactjs

I'm trying to make the the ag-grid group selectable if say for example we have the care maker and you can select the parent and everything underneath that parent also gets selected.
I've looked into the ag-grid documentation but it looks like I'm doing everything right.
Here's my code below:
import React, { useState } from 'react';
import { AgGridReact, AgGridColumn } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-balham.css';
const DummyGrid = () => {
const [gridApi, setGridApi] = useState(null);
const columnDefs = [
{
headerName: 'Make', field: 'make', checkboxSelection: true , rowGroup: true, groupSelectsChildren: true
},
{
headerName: 'Model', field: 'model'
},
{
headerName: 'Price', field: 'price'
}
]
const onGridReady = (params) => {
setGridApi(params.api);
}
const rowData = [
{ make: 'Honda', model: 'Chev', price: 6000 },
{ make: 'Honda', model: 'Civic', price: 120000 },
{ make: 'Toyota', model: 'Celica', price: 5000 },
{ make: 'Mitsubishi', model: 'GTR', price: 5000 }
]
const handleClick = () => {
const selectedNodes = gridApi.getSelectedNodes();
const selectedData = selectedNodes.map(node => node.data);
const selectedDataStringRepresentation = selectedData.map(node => node.make + ' ' + node.model).join(', ');
console.log(`The Data selected: ${selectedDataStringRepresentation}`);
}
return(
<div className="ag-theme-balham"
style={{
width: 600,
height: 600
}}>
<button onClick={handleClick}>Click this!</button>
<AgGridReact
rowData = {rowData}
columnDefs={columnDefs}
rowSelection="multiple"
onGridReady={onGridReady}
/>
</div>
);
};
export default DummyGrid;
As you can see from the output below that they're not grouping even though they the same maker. What am I doing wrong?

2 things you're doing wrong.
First, you need to import the RowGroupingModule module and pass it to your grid as modules:
import { RowGroupingModule } from '#ag-grid-enterprise/row-grouping';
modules={[RowGroupingModule]}
Secondly, you need to set groupSelectsChildren on the grid itself, not on your column definition:
groupSelectsChildren={true}
Demo.

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}

Function does not work on first render instead the series is generated after the subsequent renders

My function generateSeriesDataWithColor() seems like it does not load before the component or page renders.
So, the seriesWithColor should get the data genrated by generateSeriesDataWithColor() right away when the component is loaded but it does not get generated at the first render, instead if the component is rendered again, the colors and the graph shows up.
import HighchartsReact from "highcharts-react-official";
import Highcharts from "highcharts";
import './SkillsGraph.scss';
import { Col, Row } from "react-bootstrap";
import HeadingMain from "../../Heading/HeadingMain/HeadingMain";
export default function SkillsGraph(){
const skills = ['HTML5/CSS3/JS', 'Java11', 'PHP', 'MySql', 'MongoDB', 'ReactJS', 'ExpressJS'];
const series = {
name: 'Skill Level',
data: [ 10, 9.5, 7, 9.5, 8, 8.5, 8]
};
const seriesWithColor = generateSeriesDataWithColor(series); // This is where the series is assigned to the var
// Randomly generate colors
function generateRandomColor(){
let maxVal = 0xFFFFFF; // 16777215
let randomNumber = Math.random() * maxVal;
randomNumber = Math.floor(randomNumber);
randomNumber = randomNumber.toString(16);
let randColor = randomNumber.padStart(6, 0);
return `#${randColor.toUpperCase()}`
}
// Generate the data with random conlor
function generateSeriesDataWithColor(seriesData){
const data = seriesData.data;
const dataArray = data.map((item) => {
let color = generateRandomColor();
while(color === "#FFFFFF"){
color = generateRandomColor();
}
let dataObj = {
y: item,
color: color
}
return dataObj;
})
let seriesWithColor = {
name: 'Skill Level',
data: dataArray
}
return seriesWithColor; //This is from where the data/series is returned
}
// Options for the graph
let options = {
chart: {
type: 'bar',
height: 400
},
title: {
align: 'left',
text: 'Skills represented'
},
xAxis: {
categories: skills,
visible: true,
type: 'Skills categorised',
title: {
text: null
}
},
yAxis: {
min: 0,
max: 10,
title: {
text: 'Skill Level',
align: 'high'
},
labels: {
overflow: 'justify'
}
},
plotOptions: {
bar: {
dataLabels: {
enabled: false
}
},
column: {
colorByPoint: true
}
},
colors: [
'#ff0000',
'#00ff00',
'#0000ff',
'#0000ff',
'#0000ff',
'#0000ff',
'#0000ff'
],
legend: {
enabled: true
},
credits: {
enabled: false
},
series: seriesWithColor // This is where the generated data/series is used
}
return (
<Row>
<Col md={3}>
<HeadingMain name="This is Legend"></HeadingMain>
</Col>
<Col md={9}>
<HighchartsReact highcharts={Highcharts} options={options} className="chart"></HighchartsReact>
</Col>
</Row>
)
}
Does anyone have a solution for this?
I tried using useEffect hook to complete the wanted task but it gives an error message - 'React Hook useEffect has missing dependencies: 'generateSeriesDataWithColor' and 'series'. Either include them or remove the dependency array react-hooks/exhaustive-deps'. (Please check the code below)
const [seriesWithColor, setSeries] = useState(null);
useEffect(() => {
generateSeriesDataWithColor(series)
.then(data => setSeries(data))
.catch(err => console.log(err));
}, []);
Series needs to be an array of objects instead of a single object:
let options = {
...,
series: [seriesWithColor] // This is where the generated data/series is used
};
Live demo: https://codesandbox.io/s/highcharts-react-demo-h4r493?file=/demo.jsx
API Reference: https://api.highcharts.com/highcharts/series
You will need to call the function within useEffect hook to make sure that the data is available.

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/

How to import useCombinedRefs when using react-data-grid

I am trying to implement the draggable columns with react-data-grid based on this example: https://github.com/adazzle/react-data-grid/blob/canary/stories/demos/ColumnsReordering.tsx
I see that this example requires creating a DraggableHeaderRenderer file, so I have copied the following file into my project and converted it to React: https://github.com/adazzle/react-data-grid/blob/canary/stories/demos/components/HeaderRenderers/DraggableHeaderRenderer.tsx
My issue is that I do not know where to import useCombinedRefs from. It is not exported from react-data-grid. I see in the repo that it resides in src/hooks.
I have tried the following:
import {useCombinedRefs} from 'react-data-grid'
// Error: Attempted import error: 'useCombinedRefs' is not exported from 'react-data-grid'.
import {useCombinedRefs} from 'react-data-grid/lib/hooks';
// Error: Module not found: Can't resolve 'react-data-grid/lib/hooks' in 'C:\Users\Liam\Desktop\Work\MyProject\src\ReactDataGrid'
import useCombinedRefs from 'react-data-grid/lib/hooks/useCombinedRefs';
// Error: Module not found: Can't resolve 'react-data-grid/lib/hooks/useCombinedRefs' in 'C:\Users\Liam\Desktop\Work\MyProject\src\ReactDataGrid'
Thanks to anyone who can help.
Here is my code:
DraggableHeaderRenderer.js
import { useDrag, useDrop } from 'react-dnd';
import React from 'react'
import { SortableHeaderCell } from 'react-data-grid';
import useCombinedRefs from 'react-data-grid/lib/hooks/useCombinedRefs';
export function DraggableHeaderRenderer({ onColumnsReorder, column, sortColumn, sortDirection, onSort }) {
const [{ isDragging }, drag] = useDrag({
item: { key: column.key, type: 'COLUMN_DRAG' },
collect: monitor => ({
isDragging: !!monitor.isDragging()
})
});
const [{ isOver }, drop] = useDrop({
accept: 'COLUMN_DRAG',
drop({ key, type }) {
if (type === 'COLUMN_DRAG') {
onColumnsReorder(key, column.key);
}
},
collect: monitor => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
});
return (
<div
ref={useCombinedRefs(drag, drop)}
style={{
opacity: isDragging ? 0.5 : 1,
backgroundColor: isOver ? '#ececec' : 'inherit',
cursor: 'move'
}}
>
<SortableHeaderCell
column={column}
sortColumn={sortColumn}
sortDirection={sortDirection}
onSort={onSort}
>
{column.name}
</SortableHeaderCell>
</div>
);
}
TestDataGrid.js
import React from 'react';
import DataGrid from 'react-data-grid';
import {DraggableHeaderRenderer} from './DraggableHeaderRenderer';
import { useState, useCallback, useMemo } from 'react';
import 'react-data-grid/dist/react-data-grid.css';
const createRows = () => {
const rows = [];
for (let i = 1; i < 500; i++) {
rows.push({
id: i,
task: `Task ${i}`,
complete: Math.min(100, Math.round(Math.random() * 110)),
priority: ['Critical', 'High', 'Medium', 'Low'][Math.round(Math.random() * 3)],
issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.round(Math.random() * 3)]
});
}
return rows;
}
const createColumns = () => {
return [
{
key: 'id',
name: 'ID',
width: 80,
},
{
key: 'task',
name: 'Title',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'priority',
name: 'Priority',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'issueType',
name: 'Issue Type',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'complete',
name: '% Complete',
resizable: true,
sortable: true,
draggable: true
}
];
}
export default function TestDataGrid() {
const [rows] = useState(createRows)
const [columns, setColumns] = useState(createColumns)
const draggableColumns = useMemo(() => {
const HeaderRenderer = (props) => {
return <DraggableHeaderRenderer {...props} onColumnsReorder={handleColumnsReorder}/>
}
const handleColumnsReorder = (sourceKey, targetKey) => {
const sourceColumnIndex = columns.findIndex(c => c.key === sourceKey);
const targetColumnIndex = columns.findIndex(c => c.key === targetKey);
const reorderedColumns = [...columns];
reorderedColumns.splice(
targetColumnIndex,
0,
reorderedColumns.splice(sourceColumnIndex, 1)[0]
);
setColumns(reorderedColumns);
}
return columns.map(c => {
if(c.key === "id") return c;
return {...c, HeaderRenderer}
});
}, [columns])
return (
<DataGrid
columns={draggableColumns}
rows={rows}
/>
);
}

Resources