Related
I'm implementing a MUI datagrid with inline editing. Whenever a cell loses focus, the code sets the mode to 'view' (as it should) but then immediately the processRowUpdate callback is called (to send data to the endpoint) it sets the cell mode back to 'edit' meaning that the cell remains in edit mode.
Does anyone know why this is happening?
Maybe something to do with this processRowUpdate error logged to console?:
TypeError: Cannot read properties of undefined (reading 'id')
at getRowId (productTable.js:213:1)
at getRowIdFromRowModel (gridRowsUtils.js:17:1)
at useGridRows.js:106:1
at Array.forEach (<anonymous>)
at Object.updateRows (useGridRows.js:105:1)
at apiRef.current.<computed> [as updateRows] (useGridApiMethod.js:14:1)
at useGridCellEditing.new.js:334:1
Code:
export default function FullFeaturedCrudGrid(props) {
const [rows, setRows] = React.useState([]);
const [cellModesModel, setCellModesModel] = React.useState({})
const [selectedCellParams, setSelectedCellParams] = React.useState(null);
const { tableName } = props
const [snackbar, setSnackbar] = React.useState(null);
const handleCloseSnackbar = () => setSnackbar(null);
React.useEffect(() => {
console.log('useEffect called')
axios.get(`http://localhost:8000/mvhr/all`)
.then((response) => {
setRows(response.data);
})
}, [])
React.useEffect(() => {
console.log('cellModesModel',cellModesModel)
});
const handleCellFocus = React.useCallback((event) => {
const row = event.currentTarget.parentElement;
const id = row.dataset.id;
const field = event.currentTarget.dataset.field;
setSelectedCellParams({ id, field });
}, []);
const handleDeleteClick = (id) => () => {
axios.delete(`http://localhost:8000/delete_mvhr/${id}`
).then(() => {
setRows(rows.filter((row) => row.id !== id));
setSnackbar({ children: tableName + ' successfully deleted', severity: 'success' });
})
};
const handleCancel = () => {
if (!selectedCellParams) {
return;
}
const { id, field } = selectedCellParams;
setCellModesModel({
...cellModesModel,
[id]: {
...cellModesModel[id],
[field]: { mode: GridCellModes.View },
},
});
};
const processRowUpdate = React.useCallback(
(newRow) => {
axios.put(`http://localhost:8000/mvhr/`, newRow)
.then((response) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
return updatedRow
})
});
const handleProcessRowUpdateError = React.useCallback((error) => {
setSnackbar({ children: error.message, severity: 'error' });
}, []);
const columns = [
{ field: 'description', headerName: 'description', width: 180, editable: true },
{ field: 'elec_efficiency', headerName: 'elec_efficiency', type: 'number', editable: true },
{ field: 'heat_recovery_eff', headerName: 'heat_recovery_eff', type: 'number', editable: true },
{ field: 'range_low', headerName: 'range_low', type: 'number', editable: true },
{ field: 'range_high', headerName: 'range_high', type: 'number', editable: true },
{ field: 'superseded', headerName: 'superseded', type: 'boolean', editable: true },
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
];
return (
<Box
sx={{
height: '100vh',
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<StripedDataGrid
rows={rows}
columns={columns}
processRowUpdate={processRowUpdate}
onProcessRowUpdateError={handleProcessRowUpdateError}
onCellEditStop={handleCancel}
cellModesModel={cellModesModel}
onCellModesModelChange={(model) => setCellModesModel(model)}
components={{
Toolbar: AddToolbar,
}}
componentsProps={{
toolbar: { setRows, setSnackbar, tableName },
cell: {
onFocus: handleCellFocus,
},
}}
experimentalFeatures={{ newEditingApi: true }}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'
}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</Box>
);
}
Solved it. I refactored the code to return the updatedRow synchronously rather than as a callback to the axios request:
const processRowUpdate = React.useCallback(
(newRow) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
axios.put(`http://localhost:8000/mvhr/`, newRow)
return updatedRow
});
I currently have a DataGrid that is rendering data grabbed from my backend mongoDB. The data rendering is mapped to keys specified by the objects in the mongoDB document. In each document is a set of boolean values, and I am trying to check if any of those are true, if they are true it will render a Y in the repairsNeeded column for each row, if not, it will render an N. The main problem I am running into is where/how to check this. I have played with a few different ideas to no avail. Right now I have the repairsNeeded column for each row assigned to the document.isPowerCordDamaged (one of my booleans), which renders true or false depending on if its checked.
Code:
function Rounding() {
const [cartsRounded, setCartsRounded] = useState([]);
let navigate = useNavigate();
useEffect(() => {
userCartsRounded()
.then((response) => {
setCartsRounded(response.data);
})
.catch((err) => {
console.log(err);
});
}, []);
const columns = [
{
field: "serialNum",
headerName: "Cart Serial Number",
width: 250,
},
{
field: "pcNum",
headerName: "Workstation Number",
width: 250,
},
{
field: "dateLastRounded",
headerName: "Last Rounded On",
width: 250,
},
{
field: "repairsNeeded",
headerName: "Repairs?",
width: 100,
},
{
field: "quarter",
headerName: "Quarter",
width: 75,
},
];
const [sortModel, setSortModel] = React.useState([
{
field: "dateLastRounded",
sort: "desc",
},
]);
const rows = useMemo(
() =>
cartsRounded.map((row, index) => ({
...row,
id: index,
serialNum: row.cartSerialNumber,
pcNum: row.pcNumber,
dateLastRounded: moment(row.updatedAt).format("MM-D-YYYY"),
repairsNeeded: row.isPowerCordDamaged,
quarter: moment(row.updatedAt).format("Qo"),
})),
[cartsRounded]
);
return (
<div>
<IconButton
color="primary"
aria-label="new rounding"
component="span"
onClick={() => {
navigate("add_new_cart");
}}
>
<AddCircleOutline />
</IconButton>
<div style={{ height: 400, width: "100%" }}>
<DataGrid
component={Paper}
rows={rows}
columns={columns}
sortModel={sortModel}
pageSize={100}
rowsPerPageOptions={[100]}
/>
</div>
</div>
);
}
export default Rounding;
Document Example:
{
_id: new ObjectId("61b95e447aec51d938e856cc"),
cartSerialNumber: 'testytitit',
pcNumber: '14124f0sdf0sfs',
isPowerCordDamaged: false,
isFuseBlown: false,
isInverterBad: false,
isInterfaceDamaged: false,
isPhysicalDamage: false,
otherNotes: '',
roundedBy: '6186c13beb18d33d5088f7b2',
createdAt: 2021-12-15T03:17:24.495Z,
updatedAt: 2021-12-15T03:17:24.495Z,
__v: 0
}
I may not be understanding the question here. But I think what you are trying to do is have the repairsNeeded field have a Y or N rather than its boolean value.
Try this.
const rows = useMemo(
() =>
cartsRounded.map((row, index) => ({
...row,
id: index,
serialNum: row.cartSerialNumber,
pcNum: row.pcNumber,
dateLastRounded: moment(row.updatedAt).format("MM-D-YYYY"),
repairsNeeded: row.isPowerCordDamaged ? "Y" : "N" // short hand for if (row.isPowerCordDamaged === true) return "Y" else return "N",
quarter: moment(row.updatedAt).format("Qo"),
})),
[cartsRounded]
);
After looking it may be you want to return Y or N if any are true? In that case best pull it into a function.
const isRepairNeeded = (row) => {
if (row.isPowerCordDamaged) return "Y";
if (row.isFuseBlown) return "Y";
... rest of the ifs
return "N"; // N is only returned if none of the if statements === true;
}
const rows = useMemo(
() =>
cartsRounded.map((row, index) => ({
...row,
id: index,
serialNum: row.cartSerialNumber,
pcNum: row.pcNumber,
dateLastRounded: moment(row.updatedAt).format("MM-D-YYYY"),
repairsNeeded: isRepairNeeded(row) // call the function will return "Y" or "N",
quarter: moment(row.updatedAt).format("Qo"),
})),
[cartsRounded]
);
We have One parents componet Template in That i m using one detailsCellRendering componet for cellRedering in ag-grid..in the detailsCellRendering componet I am updating TextValue and after clicking on handleDoneValue event (done button ) the Template page should also refresh.But I am not able to do ...i need help like how can i update Template page while updating in DetailsCellRending Component.
class Template extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{
headerName: "Channel Field",
field: "field",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["reset"],
debounceMs: 1000,
suppressAndOrCondition: true, // it will remove AND/OR conditions
filterOptions: [
"contains",
"notContains",
"equals",
"notEqual",
"startsWith",
"endsWith"
]
},
cellRendererParams: {
prop1: "this is prop1"
},
cellRenderer: "agGroupCellRenderer",
},
{
headerName: "Mapping from Your Data",
field: "mapping_field",
filter: "agNumberColumnFilter",
},
{
headerName: skuValue,
field: "value",
},
{
headerName: "Status",
field: "status",
filter: 'agSetColumnFilter',
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
},
// cellRenderer: "ColourRender",
cellStyle: params => {
if (params.value === "Mapped") {
//mark mapped cells as green
return { color: 'green' };
} else {
return { color: 'red' };
}
}
},
{
headerName: "Required",
field: "required",
filter: "agSetColumnFilter",
cellRenderer: "BooleanRender",
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
}
},
],
columnDefsforSku: [
{
headerName: "Channel Field",
field: "field",
filter: "agTextColumnFilter",
filterParams: {
buttons: ["reset"],
debounceMs: 1000,
suppressAndOrCondition: true, // it will remove AND/OR conditions
filterOptions: [
"contains",
"notContains",
"equals",
"notEqual",
"startsWith",
"endsWith",
// "In List",
// "Not In List"
]
},
cellRenderer: "agGroupCellRenderer",
},
{
headerName: "Mapping from Your Data",
field: "mapping_field",
filter: "agNumberColumnFilter",
},
{
headerName: "Status",
field: "status",
filter: 'agSetColumnFilter',
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
},
cellStyle: params => {
if (params.value === "Mapped") {
//mark mapped cells as green
return { color: 'green' };
} else {
return { color: 'red' };
}
}
},
{
headerName: "Required",
field: "required",
filter: "agSetColumnFilter",
cellRenderer: "BooleanRender",
filterParams: {
values: function (params) {
// simulating async delay
setTimeout(function () {
params.success(["True", "False"]);
}, 500);
}
}
},
],
components: {
DetailCellRenderer: {DetailCellRenderer},
},
defaultColDef: {
flex: 1,
resizable: true,
// tooltipComponent: "customTooltip",
floatingFilter: true,
minWidth: 170,
sortable: true,
},
rowModelType: "serverSide",
frameworkComponents: {
customNoRowsOverlay: CustomNoRowsOverlay,
loadingRender: loadingRender,
myDetailCellRenderer: DetailCellRenderer,
customTooltip: CustomTooltip,
BooleanRender: BooleanRender,
// ColourRender: ColourRender,
},
context: { componentParent: this },
pagination: true,
serverSideStoreType: "full",
rowSelection: "multiple",
loadingCellRenderer: "loadingRender",
loadingCellRendererParams: { loadingMessage: "One moment please..." },
loadingOverlayComponent: "loadingRender",
totalProductsCount: 0,
catalogfilterCount: 0,
noRowsOverlayComponent: "customNoRowsOverlay",
noRowsOverlayComponentParams: {
noRowsMessageFunc: function () {
return "Please Select Product Preview"
}
},
getDetailRowData: function (params) {
// params.successCallback([]);
params.successCallback(params.data.callRecords);
},
sideBar: 'filters',
tooltipShowDelay: 0,
paginationPageSize: 100,
cacheBlockSize: 30,
filterStatus: false,
sweetAlert: "",
allSKu: [],
sku: localStorage.getItem("skuPreviewProduct"),
isProductPrevewSelected: false,
skuInputValue: "",
loading: false,
product_id: localStorage.getItem("products_ids") ? localStorage.getItem("products_ids") : null,
// loadingForDetails: localStorage.getItem("loadings")
};
<Grid item xs={10}>
<div
id="myGrid"
style={{
height: "90%",
width: "90%",
}}
className="ag-theme-alpine template"
>
<AgGridReact
columnDefs={this.state.product_id ? columnDefs : columnDefsforSku}
defaultColDef={defaultColDef}
onGridReady={this.onGridReady}
rowModelType={rowModelType}
masterDetail={true}
enableCellChangeFlash={true}
masterDetail={true}
detailCellRenderer={'myDetailCellRenderer'}
detailRowHeight={350}
height={400}
width={600}
animateRows={true}
frameworkComponents={frameworkComponents}
embedFullWidthRows={true}
loadingOverlayComponent={loadingOverlayComponent}
loadingCellRenderer={loadingCellRenderer} // default loading renderer
loadingCellRendererParams={loadingCellRendererParams} // renderer Params to load msg
noRowsOverlayComponent={noRowsOverlayComponent} // default no rows overlay component
noRowsOverlayComponentParams={noRowsOverlayComponentParams} // to show default no rows message
paginationPageSize={this.state.paginationPageSize} // pagination page size
onColumnVisible={this.onColumnVisible.bind(this)}
getDetailRowData={getDetailRowData}
cacheBlockSize={cacheBlockSize}
detailCellRendererParams={detailCellRendererParams }
/>
</div>
</Grid>
</Grid>
</div>
)
1. **List item**
}
// DetailsCellRendering page
import React, { useEffect, useState } from 'react';
import Paper from '#material-ui/core/Paper';
import Grid from '#material-ui/core/Grid';
import { makeStyles } from '#material-ui/core/styles';
import TemplateTab from "../views/FunctionalComponents/TemplateTab"
import { TEMPLATE_MARKETPLACE_FEILD_MAPPING, TEMPLATE_API } from "../configurations/configApi";
import { apiEdit, fetchUrl } from "../apiActions/action";
import LoadingOverlay from "../components/overlays/LoadingOverlay";
import Templates from 'views/Tables/Templates';
// import Template from 'views/Tables/Templates';
const useStyles = makeStyles(() => ({
root: {
flexGrow: 1,
textAlign: "center",
marginBottom: 20,
}
}));
const DetailCellRenderer = ({ data, params, api, node}) => {
const classes = useStyles();
const [allData, setAllData] = useState();
const [loading, setLoading] = useState(false);
const marketplaceId = localStorage.getItem("marketplaceId") ? localStorage.getItem("marketplaceId") : "" ;
const skuValue = localStorage.getItem("skuPreviewProduct") ? localStorage.getItem("skuPreviewProduct") : "";
const fetchAllTemplate = () => {
setLoading(true);
var soniServe = `${TEMPLATE_API}${marketplaceId}/?sku=${skuValue}&filter=${"[]"}`;
fetchUrl(soniServe, ({ status, response }) => {
if (status) {
let refreshValue = response.data.response[0].mappings.filter(item => data.id === item.id);
localStorage.setItem("cellRenderId", refreshValue && refreshValue[0]&& refreshValue[0].id )
setAllData(refreshValue && refreshValue[0])
setLoading(false)
} else {
if (response.response &&
response.response.status &&
((response.response.status === 401))
) {
localStorage.removeItem("token");
localStorage.removeItem("email");
window.location = "/auth/login-page";
}
}
});
}
useEffect(() => {
setAllData(data)
}, data)
// update text value
const handleDoneValue = (textValue) => {
const productId = allData && allData.id;
let value = textValue
// updating the existing labels
const urlForSave = `${TEMPLATE_MARKETPLACE_FEILD_MAPPING}${productId}/`;
const methodOfSave = "put";
const data = { "mappings": { "text_value": value } }
apiEdit(urlForSave, data, methodOfSave, ({ status, response }) => {
if (status) {
// window.location = `/configuration/details/${marketplaceId}`
fetchAllTemplate();
// inputMethod === "saveAs" && this.fetchProductDisplay("saveAs");
} else {
if (response.response &&
response.response.status &&
((response.response.status === 401))
) {
localStorage.removeItem("token");
localStorage.removeItem("email");
window.location = "/auth/login-page";
} else if (
(response &&
response.status &&
(response.status === 404 ||
response.status === 500)) ||
!response
) {
window.location = "/error";
} else {
// to show some popups
window.location = "/error";
}
}
});
}
return (
<div>
<div className={classes.root}>
<Grid container style={{ paddingTop: "10px" }}>
<Grid item xs={3}>
{allData && allData.field ? allData.field : null}
</Grid>
<Grid item xs={3}>
{ allData && allData.mapping_field ? allData.mapping_field : null}
</Grid>
<Grid item xs={3}>
{urlCheck && urlCheck[0] === "https" || urlCheck && urlCheck[0] === "http"? {allData && allData.value} : allData && allData.value ? allData.value : null}
{/* { data && data.value ? data.value : null} */}
</Grid>
<Grid item xs={3} >
{allData && allData.status}
</Grid>
</Grid>
</div>
<div className={classes.root}>
<Grid container spacing={1}>
<Grid item xs={4} justify="flex-end" style={{ display: "flex" }}>
Description
</Grid>
<Grid item xs={8} >
<Paper className="templateBox">
<span style={{ marginBottom: "20px" }}>What value should we send for each product's "{data.field}"? </span>
<TemplateTab allCellData={data} handleDoneValue={handleDoneValue}
/>
</Paper>
</Grid>
</Grid>
</div>
<LoadingOverlay showOverlay={loading} />
</div>
);
};
export default DetailCellRenderer;
I want to have the required field validation for a custom cell, i have in my data table. With the getprops i am able to style the cell but how can i display like a helper text or error for that cell? Is there any example?
Example:
const columns = [
{
Header: 'Things to Do',
accessor: 'item',
getProps: (rowInfo) => {
return {
style: {
backgroundColor: rowInfo && rowInfo.row.item === 'Do a thing' ? 'red' : null,
},
}
}
},
{
Header: '2020-04-01',
accessor: 'date.2020-04-01',
getProps: (rowInfo) => {
return {
style: {
backgroundColor: rowInfo && rowInfo.row['date.2020-04-01'] === 'Y' ? 'red' : null,
},
}
}
},
I am using react-table plugin, and my cells keep re-rendering while I am changing the applied searching filter even though their values aren't changed.
As seen in the video, name and debt TDs keep updating, even though their values are static.
https://i.imgur.com/2KkNs9f.mp4
I imagine, that may impact performance on larger tables.
Is there any fix? Or am I doing anything wrong?
Thanks.
Edit:
Code has been requested. Not much to show, basically everything done as in documentation.
Rendering part:
render(){
const columns = [
{
Header: "Name",
accessor: "name",
className: css.cell,
headerClassName: css.cell,
filterMethod: (filter, row) => {
return row[filter.id]
.toLowerCase()
.includes(filter.value.toLowerCase());
},
Cell: props => {
return <span>{props.value}</span>;
}
},
{
Header: "Tc",
accessor: d => {
const date = d.lastChange;
if (date.length) {
const s = date.split(" ");
const s2 = s[0].split("/");
const mdy = [s2[1], s2[0], s2[2]].join("/");
const his = s[1];
return Date.parse(mdy + " " + his);
}
return "";
},
id: "lastChange",
Cell: props => {
return <ArrowTime lastChange={props.value}></ArrowTime>;
},
headerClassName: css.cell,
className: css.cell
},
{
Header: "Debt",
accessor: d => Number(d.lastDebt),
id: "lastDebt",
headerClassName: css.cell,
className: css.cell,
Cell: props => {
return <span className="number">{props.value}</span>;
},
getProps: (state, rowInfo, column) => {
return {
style: {
background: rowInfo ? this.getColor(rowInfo.row.lastDebt) : null
}
};
}
}
];
return (
<ReactTable
data={this.props.table}
columns={columns}
minRows={0}
showPagination={false}
NoDataComponent={CustomNoDataComponent}
className={css.table}
resizable={false}
filtered={[{ id: "name", value: this.props.filter }]}
getTrProps={(state, rowInfo) => {
return {
className: rowInfo ? css.subRow : ""
};
}}
getTrGroupProps={(state, rowInfo) => {
return {
className: rowInfo ? css.row : ""
};
}}
getTbodyProps={props => {
return {
className: props ? css.tbody : ""
};
}}
getTheadProps={props => {
return {
className: props ? css.thead : ""
};
}}
defaultSorted={[
{
id: "lastDebt",
desc: true
}
]}
/>
);
}