How to loop data and print in a table using material ui - reactjs

I am working with Material-UI and getting data from the backend. There is no issue with the backend, but I don't know how to loop data and print it in a table format using Material-UI.
Can anyone guide me on how to print data in a table format?
Here is my code so far:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { getProducts } from "../../services/products";
import MaterialTable, { MTableToolbar } from "material-table";
const productsList = props => {
const [data, setData] = useState([]);
const [state] = React.useState({
columns: [
{ title: "Brand", field: "brand" }, //assume here my backend schema is brand
{ title: "Price", field: "price" }, //here price
{ title: "Model no", field: "model" } //here model
]
});
const getProducts = async () => {
try {
const res = await getProducts();
setData(res.data);
console.log(res.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<MaterialTable
components={{
Toolbar: props => {
return (
<div>
<MTableToolbar {...props} />
</div>
);
}
}}
options={{
actionsColumnIndex: 5,
selection: true
}}
/>
);
};
export default function Company() {
return <productsList />;
}

You have to set the data and columns value. So try it like this:
import React, { useState, useEffect } from "react";
import MaterialTable, { MTableToolbar } from "material-table";
const fakeFetch = () => {
return new Promise(resolve => {
resolve({
data: [
{ brand: "brand 1", price: 1, model: "123" },
{ brand: "brand 2", price: 1, model: "456" },
{ brand: "brand 3", price: 1, model: "789" }
]
});
});
};
export default function App() {
const [data, setData] = useState([]);
// When the columns don't change you don't need to hold it in state
const columns = [
{ title: "Brand", field: "brand" }, //assume here my backend schema is brand
{ title: "Price", field: "price" }, //here price
{ title: "Model no", field: "model" } //here model
];
const getProducts = async () => {
try {
const res = await fakeFetch();
setData(res.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<MaterialTable
columns={columns} // <-- Set the columns on the table
data={data} // <-- Set the data on the table
components={{
Toolbar: props => {
return (
<div>
<MTableToolbar {...props} />
</div>
);
}
}}
options={{
actionsColumnIndex: 5,
selection: true
}}
/>
);
}
To make it even easier you could also provide your fetch function (fakeFetch in this case) as the data value;
data={fakeFetch} // <-- Using this you wouldn't need the [data, setData], getProducts and useEffect code.
Working sandbox link

As per the material-table approach, you have to put your whole fetched data on the data prop inside the MaterialTable component. So as far as I can understand, there is no looping made in this case by using the material-table library.
Assuming the attributes in your data object match the field names specified in your columns prop (if not, create an array of objects from your fetched data that matches the column fields or vice-versa).
And the code would be just the addition of the data prop in your table:
<MaterialTable
// ... existing props
data={data}
/>
Keep in mind that you could also use the remote data approach as described in the documentation which gives you the means to immediately query your data and fetch it inside the data prop of the table.

Related

My cell renderer does not have any data because of an async call

So I have a component which uses a CellRenderer which gets some data:
import { useEffect, useMemo, useState } from "react";
import "ag-grid-community/dist/styles/ag-grid.min.css";
import "ag-grid-community/dist/styles/ag-theme-material.min.css";
import Grid from "../Common/Grid";
import axios from "axios";
import SelectJudetCellRenderer from "./SelectJudetCellRenderer";
function GetJudete() {
return axios
.get("http://localhost:5266/api/judete")
.then((response) => {
let data = response.data;
return data;
})
.catch((err) => {
console.log("Eroare la aducerea datelor.");
});
}
function Localitati() {
let [judete, setJudete] = useState([]);
useEffect(() => {
async function GetJudeteAsync() {
const result = await GetJudete();
setJudete(result);
}
GetJudeteAsync();
}, []);
const [columnDefs] = useState([
{ field: "nume", filter: "agTextColumnFilter", editable: true },
{ field: "judet", filter: "agTextColumnFilter", editable: true, cellRenderer: SelectJudetCellRenderer, cellRendererParams: {judete: judete} },
]);
return (
<Grid
baseLink="http://localhost:5266/api/localitati"
columnDefs={columnDefs}
/>
);
}
export default Localitati;
Here's my Cell renderer:
import { ICellRendererParams } from 'ag-grid-community';
export interface JudeteCellRendererParams extends ICellRendererParams {
judete: any[];
}
function SelectJudetCellRenderer(props: JudeteCellRendererParams) {
console.log(props.judete)
return (
<select name="judete">
{
props.judete.map((judet) =>
<option value={judet.id}>{judet.name}</option>
)
}
</select>
)
}
export default SelectJudetCellRenderer;
The problem is that after the Async call Judete is getting new data but my cell renderer does not get the new data.
The console.log() from the CellRenderer returns an empty array.
Why is this happening and how can I fix it?
Thanks.
You need to tell AG Grid to refresh the rendered cell, this is not very well documented, see https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/#cell-renderer-component
Here is a simple example using Angular (should be similar for class based React)
Notice the refresh() method:
// gets called whenever the user gets the cell to refresh
refresh(params: ICellRendererParams) {
// set value into cell again
this.cellValue = this.getValueToDisplay(params);
}
https://plnkr.co/edit/yFqQHfNjxMLrPb9f.
For functional components you should explicitly call the api.refreshCells() when the data is available.
See here for more details: https://www.ag-grid.com/react-data-grid/component-cell-renderer/#component-refresh
A possible solution (although I think it would be more simple to switch to a class component renderer)
function Localitati() {
let [judete, setJudete] = useState([]);
// get hold of AG Grid gridApi
const gridApiRef = React.useRef<GridApi>();
// update the 'judete' column when new data is available (this will re-invoke the cell renderers)
useEffect(() => {
gridApiRef.current.refreshCells({columns: 'judet'});
}, [judete]);
useEffect(() => {
async function GetJudeteAsync() {
const result = await GetJudete();
setJudete(result);
}
GetJudeteAsync();
}, []);
const [columnDefs] = useState([
{ field: "nume", filter: "agTextColumnFilter", editable: true },
{ field: "judet", filter: "agTextColumnFilter", editable: true, cellRenderer: SelectJudetCellRenderer, cellRendererParams: {judete: judete} },
]);
return (
<Grid
baseLink="http://localhost:5266/api/localitati"
columnDefs={columnDefs}
onGridReady={({ api }) => {
gridApiRef.current = api;
}}
/>
);
}

ChartJS data is unsynchronised with labels

I am trying to get the chart data from graphql and render it to the DOM.
All is fine as the data is showing, but is not synchronized with it's real values.
As I had to extract the data into a unique set of arrays, the data is not synchronized with it's underlying values.
How can I improve my component, so the data is in sync with it's labels?
import React, { useEffect, useState, useMemo } from "react";
import { useQuery, gql } from "#apollo/client";
import { Line, Pie, Radar, Bar } from "react-chartjs-2";
import { Chart, registerables } from "chart.js";
Chart.register(...registerables);
const total = gql`
query GetIntell($myLimit: Int!) {
webs(
pagination: { start: 0, limit: $myLimit }
filters: { site: { eq: "nist" } }
) {
data {
id
attributes {
dateAdded
severity
}
}
}
}
`;
export default function Graph({limit}) {
const { loading, error, data } = useQuery(total, {
variables: { myLimit: limit },
});
const [chartData, setChartData] = useState({});
const myLabels = useMemo(
() => (data ? data.webs.data.map((t) => t.attributes.dateAdded) : null),
[data]
);
const myData = useMemo(
() => (data ? data.webs.data.map((t) => t.attributes.severity) : null),
[data]
);
useMemo(() => {
setChartData({
labels: [...new Set(myLabels)], <---I am altering the data with this set. How can I recover the data with it's correct values and still get the unique array of values?
datasets: [
{
label: "Nist",
data: myData,
fill: true,
},
],
});
}, [data]);
let delayed;
if (loading) return <div className="loader center"></div>;
if (error) return <p>`Error! ${error}`</p>;
return (
<div className="container">
<div className="chart">
<Bar
data={chartData}
options={{
responsive: true,
plugins: {
title: {
display: true,
text: "Severity Accumulation",
},
legend: {
display: true,
position: "top",
},
},
}}
/>
</div>
</div>
);
}
Please see my pic bellow. The chart is fine, but still incorrect as it displayes values maybe for other days.
Any feedback is well received! Also if you can see an easier way of improving my code, I would owe you a lot!

Edit custom column component while adding new row of Material Table

With the React Material Table library, is it possible to render a custom component while adding a new row? I'm using a custom component (a Material UI select box, actually), for the Expected Result column. When I add a new row, I only see a field for the Requirement column, not the Expected Result column. Is it possible to add an input for the Expected Result column of the new row as well?
Another option is to not use custom components at all and instead use something like the Cell Editable Example of https://material-table.com/#/docs/features/editable. However, I'm not a fan of the extra clicks that it takes to edit the Expected Result, compared to directly using a Select field.
import MaterialTable from 'material-table'
import { MenuItem, Select } from '#material-ui/core'
import React, { useState } from 'react'
import update from 'immutability-helper'
type PassFailNA = 'Pass' | 'Fail' | 'N/A'
type RowData = {
requirementId: number,
requirementName: string,
expectedResult: PassFailNA,
expectedResultId?: number
}
export function ExpectedResultsTable(props: {
scenarioId: number
}) {
const [tableData, setTableData] = useState<RowData[]>([{ requirementId: 1, requirementName: 'hello', expectedResult: 'Pass' }])
const { enqueueSnackbar } = useSnackbar()
const handleSelect = (id: number) => (event: React.ChangeEvent<{ name?: string; value: any }>) => {
setTableData((tableData: RowData[]) => {
const rowNum = tableData.findIndex(x => x.requirementId === id)
return update<RowData[]>(tableData, {
[rowNum]: { expectedResult: { $set: event.target.value } }
})
})
}
return (
<MaterialTable<RowData>
title=""
columns={[
{
title: 'Requirement',
field: 'requirementName'
},
{
title: 'Expected Result',
field: 'expectedResult',
render: (rowData) => (
<Select value={rowData.expectedResult} onChange={handleSelect(rowData.requirementId)}>
<MenuItem value="Pass">Pass</MenuItem>
<MenuItem value="Fail">Fail</MenuItem>
<MenuItem value="N/A">N/A</MenuItem>
</Select>
)
}
]}
data={tableData}
editable={{
onRowAdd: newRow =>
new Promise((resolve, reject) => {
setTimeout(() => {
setTableData(tableData => update(tableData, { $push: [{ ...newRow, expectedResult: 'N/A'}] }))
resolve()
}, 1000)
})
}}
/>
)
}
To achieve what you are looking for, I think you should specify the editComponent property ( besides render ) when defining the column. That prop takes a function where you can define the component used during the edit or creation phase.
Here is an example I made with a boolean input:
const tableColumns = [
{ title: "Client", field: "id" },
{ title: "Name", field: "name" },
{
title: "booleanValue",
field: "booleanValue",
editComponent: (props) => {
console.log(props);
return (
<input
type="checkbox"
checked={props.value}
onChange={(e) => props.onChange(e.target.checked)}
/>
);
},
render: (rowdata) => (
<input type="checkbox" checked={rowdata.booleanValue} />
)
}
];
Link to working sandbox. I hope that works for you!

Reactjs - Make the get request when loading the page

I know that in Angular there is ngOnInit. In Reactjs is there something similar to do the get request when loading the page?
I have a table and I need to get the request when I load the page for me to display the data in the table.
export default function ListAdverts() {
const columns = [
{
label: "Título",
accessor: "title",
width: "194px"
},
{
label: "Valor",
accessor: "price_cents",
width: "131px"
},
{
label: "Anunciante",
accessor: "title",
width: "203px"
},
{
label: "Categoria",
accessor: "title",
width: "158px"
}
];
const [dataAdverts, setdDataAdverts] = React.useState([]);
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}
Data fetching for components is usually done inside the useEffect hook
export default function ListAdverts() {
const columns = ...
const [dataAdverts, setdDataAdverts] = React.useState([]);
// fetch data here
// runs only once because of empty dependency array
React.useEffect(() => {
let isCancelled = false
const fetchSomeData = async () => {
const data = await someApiRequest()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setdDataAdverts(data)
}
}
fetchSomeData()
// cleanup
return () => {
isCancelled = true
}
}, [])
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}

Checkbox for specific row in react-table?

import React, { Component } from 'react';
import { connect } from 'react-redux';
import getSchoolsList from '../Actions/Index';
import ReactTable from "react-table";
import checkboxHOC from "react-table/lib/hoc/selectTable";
import "react-table/react-table.css";
const CheckboxTable = checkboxHOC(ReactTable);
class Home extends Component {
constructor(props){
super(props);
this.state = {
selection: [],
selectAll: false
};
}
componentDidMount(){
this.props.getSchoolsList();
}
toggleSelection = (key, shift, row) => {
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
if (keyIndex >= 0) {
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
selection.push(key);
}
this.setState({ selection });
};
toggleAll = () => {
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
const wrappedInstance = this.checkboxTable.getWrappedInstance();
const currentRecords = wrappedInstance.getResolvedState().sortedData;
currentRecords.forEach(item => {
selection.push(item._original._id);
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
console.log(key);
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
render() {
const { toggleSelection, toggleAll, isSelected, logSelection } = this;
const { selectAll } = this.state;
const checkboxProps = {
selectAll,
isSelected,
toggleSelection,
toggleAll,
selectType: "checkbox",
};
const data = this.props.StateData?this.props.StateData.data:[];
const {loading, StateData} = this.props;
if (loading) {
{console.log(loading)}
return <div>Loading...</div>;
}
return (
<div>
{console.log(this.checkboxTable)}
<button onClick={logSelection}>Log Selection</button>
<CheckboxTable
ref={r => (this.checkboxTable = r)}
data={data}
columns={[
{
Header: "School Name",
accessor: "name"
},
{
Header: "Location",
id: "lastName",
accessor: d => d.area + ',' + d.city
},
{
Header: "Curriculum",
accessor: "curriculum"
},
{
Header: "Grade",
accessor:"grade"
},
{
Header: "Web App_URL",
accessor: "webapp_url",
},
{
Header: "Status",
id: "status",
accessor: d =>{
if(d.publish === true){
console.log(d.publish)
return 'Publish';
}else{
return 'Unpublished'
}
}
}
]}
defaultPageSize={10}
className="-striped -highlight"
{...checkboxProps}
/>
</div>
);
}
}
function mapStateToProps (state) {
return {
StateData:state.login.schools,
loading: state.login.loading,
}
};
export default connect(mapStateToProps, {getSchoolsList})(Home);
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working <https://codesandbox.io/s/7yq5ylw09j?from-embed>, but whenever i add my dynamic data it is not working.
Hi all, can someone help me with this what is the wrong i am not getting individual checkboxes in this ? i checked this link code in my local it is working https://codesandbox.io/s/7yq5ylw09j?from-embed, but whenever i add my dynamic data it is not working.
If your using TypeScript and tslint this happens via the example for select table(checkboxes) getdata() does this:
const _id = chance.guid();
return {
_id,
...item
};
tslint complains about the _id var naming with "variable name must be in lowerCamelCase, PascalCase or UPPER_CASE"
You can see that at: https://react-table.js.org/#/story/select-table-hoc
So you have to change _id to id if you want to get past tslint. Changing from _id to id breaks the default keyField logic in react-table which wants _id. That necessitates setting the keyField property to "id".
If you do not mention unique key id by default it will take "_id" as the key field. By defining a key value you can overcome the above mentioned matter as follows.
Let's say there is a specific column named "USER ID". And we'll take the accessor of the column as "uid".
The code should be modified as follows.
Checkbox Table
<CheckboxTable
keyField="uid"
......Rest of your code....
/>
toggleAll()
toggleAll() {
..........code...........
currentRecords.forEach(item => {
selection.push(item.uid);
});
}
.......code............
}

Resources