Edit custom column component while adding new row of Material Table - reactjs

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!

Related

DataGrid returns only 1 result instead of more

I'm trying to display all the results on this data table from MUI but it is only displaying 1 result instead of all and I don't see what I'm doing wrong.
If you have good ideas and best practices on how to do the table would I would be very appreciated.
import React, { FC } from "react";
import { DataGrid, GridColDef, GridRowsProp } from "#mui/x-data-grid";
interface Props {
processes: any;
}
const DataTable: FC<Props> = ({ processes }) => {
const inCourse = processes.filter(function (process: any) {
return process.isActive === 0;
});
const columns: GridColDef[] = [
{ field: "id", headerName: "Id", hide: true },
{ field: "NProc", headerName: "N/Proc" },
{ field: "VRef", headerName: "V/Proc" },
];
const rows: GridRowsProp = [
{
id: inCourse.map((process: any) => {
return process.id;
}),
VRef: inCourse.map((process: any) => {
return process.houseType;
}),
NProc: inCourse.map((process: any) => {
return process.processNumber;
}),
},
];
return (
<div className="gridboard">
<DataGrid rows={rows} columns={columns} pageSize={5} checkboxSelection />
</div>
);
};
export default DataTable;
The rows array contains only one value so DataGrid display one row, you probably want something like this. See Array.map() for more detail:
const rows: GridRowsProp = inCourse.map(c => ({
id: c.id,
VRef: c.houseType,
NProc: c.processNumber,
}));

Sorting a ReactTable based on a Switch being active

I need to sort a react table with the rows where a Switch is checked appearing at the top.
Here is the code I have so far.
import React from 'react';
import ReactTable from 'react-table';
import { useDispatch } from 'react-redux';
import { Switch } from '#material-ui/core';
import * as Actions from 'app/routes/actions/store/actions';
import { Pagination, FilterCaseInsensitive } from 'app/library';
export const PromotionControl = (props) => {
const dispatch = useDispatch();
const model = props.model;
const setModel = props.setModel;
const pageState = props.pageState;
const promotions = props.promotions;
function hasPromotion(promotion) {
if (model.actionPromotions === undefined)
return false;
const exists = model.actionPromotions.find(t => t.clientPromotionId === promotion.clientPromotionId);
return exists !== undefined;
}
function handlePromotionSelection(promotion) {
let selectedPromotions;
if (hasPromotion(promotion)) {
selectedPromotions = model.actionPromotions.filter(t =>
t.clientPromotionId !== promotion.clientPromotionId
);
}
else {
selectedPromotions = [...model.actionPromotions, {
clientPromotionId: promotion.clientPromotionId,
promotionName: promotion.promotionName
}];
}
setModel(values => ({ ...values, actionPromotions: selectedPromotions }));
}
return props.promotions.length <= 0 ? (<h5>No promotions configured.</h5>) : (
<React.Fragment>
<ReactTable
data={promotions}
PaginationComponent={Pagination}
defaultPageSize={5}
className="-striped -highlight"
minRows={0}
filterable={pageState.filterable}
sortable
filtered={pageState.filtered}
defaultFilterMethod={FilterCaseInsensitive}
onFilteredChange={(filtered) => dispatch(Actions.filterPromotions(filtered))}
columns={[
{
Header: "Client Promotion ID",
accessor: 'clientPromotionId',
width:200
},
{
Header: "Type",
accessor: 'promotionType',
maxWidth: 300
},
{
Header: "Name",
accessor: "promotionName"
},
{
maxWidth: 70,
style: { justifyContent: 'center' },
Cell: item => {
return (
<Switch
checked={hasPromotion(item.original)}
onClick={() => handlePromotionSelection(item.original)}
/>
)
}
}
]}
/>
</React.Fragment>
);
};
The column I want to sort the table by is the last column. At the moment the switch is checked based on the result of a check performed by the hasPromotion function. The issue is I don't know how to sort the whole table based on this column with the checked ones appearing at the top of the table.
Any ideas would be greatly appreciated.
As in Javascript, we have :
true - false === 1
false - true === -1
You could just base your sort on these values :
yourDataMatrix = yourDataMatrix.sort((a,b) => b.values[indexOfColumnToSort] - a.values[indexOfColumnToSort]);
Since I don't know the structure of your data, this code is not working, but you can use the logic in your code

MobX ReactJS AntD updates won't re-render

So I'm trying change some table data within my AntD Table using ReactJS and MobX. The data in my MobX observable changes, but the table doesn't re-render an update until I say.... resize the page and the table re-renders. I've recreated my issue on CodeSandbox - it's not the exact data type, but this is the EXACT issue I'm running into, any thoughts???
https://codesandbox.io/s/remove-items-from-mobx-array-forked-3nybr?file=/index.js
#action
change = (key) => {
this.data
.filter(item => key === item.key)
.forEach(piece => {
piece.key = 10;
});
console.log(this.data);
};
const FooTable = () => {
const columns = [
{
title: "ID",
dataIndex: "key"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Last Name",
dataIndex: "lastName"
},
{
title: "Actions",
render: (text, record) => {
return (
<Button
type="link"
icon="delete"
onClick={() => tableStore.change(record.key)}
>
Delete
</Button>
);
}
}
];
return useObserver(() => {
return <Table columns={columns} dataSource={tableStore.data} />;
});
};
Because AntD Table is not observer by itself you need to use toJS on your data before you pass it to AntD.
import { toJS } from "mobx";
// ...
const FooTable = () => {
const columns = [ ... ];
return useObserver(() => {
return <Table columns={columns} dataSource={toJS(tableStore.data)} />;
});
};

Using a function in Material-Table render property

I need to use a custom function in Material-Table column render property.
The function gets called, I get printed on the console the expected results, however, the result would simply not render in the table.
Here is the code:
import React from 'react';
import HraReferenceDataContext from '../context/hraReferenceData/hraReferenceDataContext';
import MaterialTable from 'material-table';
const EmployeeDetailsCompanyDocuments = ({ companyDocumentsData }) => {
const hraReferenceDataContext = React.useContext(HraReferenceDataContext);
const { companyDocumentTypes } = hraReferenceDataContext;
const getDocumentTypeForRow = id => {
companyDocumentTypes.forEach(type => {
if (type.id === id) {
console.log(type.name)
return type.name;
}
});
};
const columnInfo = [
{
field: 'typeId',
title: 'Type',
render: rowData =>{ getDocumentTypeForRow(rowData.typeId)}, //here is the problem
},
{ field: 'created', title: 'Created On' },
];
return (
<MaterialTable
columns={columnInfo}
data={companyDocumentsData}
title="Company Documents List"
/>
);
};
Returning inside forEach doesn't work.
change this function
const getDocumentTypeForRow = id => {
companyDocumentTypes.forEach(type => {
if (type.id === id) {
console.log(type.name)
return type.name;
}
});
};
to
const getDocumentTypeForRow = id => {
return companyDocumentTypes.find(type => type.id === id).name;
};
update
change
render: rowData =>{ getDocumentTypeForRow(rowData.typeId)},
to
render: rowData => getDocumentTypeForRow(rowData.typeId),
because you should return the value that is returned from getDocumentTypeForRow.

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

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.

Resources