How to pass state between renderCell components in MUI Data Grid - reactjs

How can I change the MenuItems of one Select when another Select component changes using DataGrid? I need to be able to pass the state of one Select component to the other, but I'm not sure how when using renderCell.
For example, let's say I have the following object:
const data = {
"/path/to/file1.csv": {
parameters: ["Parameter 1", "Parameter 2", "Parameter 3"],
},
"/path/to/file2.csv": {
parameters: ["Parameter 2", "Parameter 3", "Parameter 4"],
},
"/path/to/file3.csv": {
parameters: ["Parameter 5", "Parameter 6", "Parameter 7"],
},
};
In my DataGrid table, every time I add a new row with the click of a button, the first cell has a Select component containing Object.keys(data).
The second cell contains another Select component. I want this Select component to contain parameters that are dependent on the value selected. For example, if /path/to/file1.csv is selected, I want to make available those parameters (Parameter 1, Parameter 2, Parameter 3), but if /path/to/file3.csv is selected, I want to make available those parameters (Parameter 5, Parameter 6, Parameter 7).
Here's my component:
import * as React from "react";
import PropTypes from "prop-types";
import { Button, Select, MenuItem } from "#mui/material";
import DeleteIcon from "#mui/icons-material/Delete";
import { DataGrid, GridActionsCellItem } from "#mui/x-data-grid";
const FileSelect = (props) => {
const { value } = props;
const [file, setFile] = React.useState("");
const handleChange = (event) => {
setFile(event.target.value);
};
return (
<Select id="file-select" value={file} onChange={handleChange} fullWidth>
{value?.map((item, index) => (
<MenuItem key={index} value={item}>
{item}
</MenuItem>
))}
</Select>
);
};
FileSelect.propTypes = {
value: PropTypes.array,
};
const ParameterSelect = (props) => {
const { value } = props;
const [parameter, setParameter] = React.useState("");
const handleChange = (event) => {
setParameter(event.target.value);
};
return (
<Select
id="parameter-select"
value={parameter}
onChange={handleChange}
fullWidth
>
{value?.map((item, index) => (
<MenuItem key={index} value={item}>
{item}
</MenuItem>
))}
</Select>
);
};
export default function DataGridTable(props) {
const { data } = props;
const files = Object.keys(data);
const [rows, setRows] = React.useState([]);
const columns = [
{
field: "file",
headerName: "File",
// width: 200,
flex: 1,
renderCell: FileSelect,
},
{
field: "x",
headerName: "X",
// width: 200,
flex: 0.5,
renderCell: ParameterSelect,
},
{
field: "actions",
headerName: "Delete",
type: "actions",
width: 80,
getActions: (params) => [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={deleteRow(params.id)}
/>,
],
},
];
const handleClick = () => {
const newRow = {
id: rows.length + 1,
file: files,
x: [],
};
setRows((prevState) => [...prevState, newRow]);
};
const deleteRow = React.useCallback(
(id) => () => {
setTimeout(() => {
setRows((prevRows) => prevRows.filter((row) => row.id !== id));
});
},
[]
);
return (
<div>
<Button variant="contained" onClick={handleClick}>
Add row
</Button>
<div style={{ height: 300, width: "100%" }}>
<DataGrid rows={rows} columns={columns} disableSelectionOnClick />
</div>
</div>
);
}

The simplest way that I could think to accomplish this is by adding an extra field to the column definition as an "easy" place to store the selected value.
...
const FileSelect = (props) => {
const { value, row } = props;
const [file, setFile] = React.useState("");
const handleChange = (event) => {
setFile(event.target.value);
// Set the value here
row.selectedFile = event.target.value;
};
return (
<Select id="file-select" value={file} onChange={handleChange} fullWidth>
{value?.map((item, index) => (
<MenuItem key={index} value={item}>
{item}
</MenuItem>
))}
</Select>
);
};
...
{
field: "selectedFile",
hideable: true
},
...
Then set the selected value (file) in the FileSelect parent value in the selectedFile column. Then all that was left to do was to make the parameters lookup values available to the ParameterSelect. Again, I just stuffed them into the renderCell props, but this could be done better as well:
...
{
field: "x",
headerName: "X",
flex: 0.5,
// Passing the entire original data in as an extra param, for demonstration purposes
renderCell: (props) => ParameterSelect({ ...props, data })
},
...
Finally, just hide the selectedFile column:
...
<DataGrid
rows={rows}
columns={columns}
disableSelectionOnClick
// Hiding the extra field
columnVisibilityModel={{
selectedFile: false
}}
/>
...
Producing this: (I changed your values to make them easier to read while I was working)
Working CodeSandBox: https://codesandbox.io/s/prod-sun-bdvcu0?file=/demo.js:842-854

Related

How to get selected checkbox value and id from the multi select in MUI

I am using MUI select component with multi-select. The multi-select rendering uses the below JSON array. Is there any way we can get both the selected item id and value?
Here is the code sandbox URL :https://codesandbox.io/s/cranky-currying-ce8djy?file=/src/App.js
JSON Array: const menuItems = [
{ id: "8271e42a-8982-44b8-9745-3271e0cf9d12", value: "Item 1" },
{ id: "c0f3e462-02f8-4d8d-ae05-6965b7902a80", value: "Item 2" },
{ id: "c0f3dfyh-02f8-4d8d-ae05-6965b7dw3456", value: "Item 3" }
];
export default function App() {
const [selected, setSelected] = useState([]);
const handleChange = (event) => {
const {
target: { value }
} = event;
setSelected(typeof value === "string" ? value.split(",") : value);
console.log(selected);
};
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const lteProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
}
};
return (
<div className="App">
<FormControl fullWidth={true}>
<Select
multiple
value={selected}
onChange={handleChange}
renderValue={(selected) => selected.join(", ")}
MenuProps={lteProps}
>
{menuItems.map((item) => (
<MenuItem key={item.id} value={item.value}>
<Checkbox checked={selected.indexOf(item.value) > -1} />
<ListItemText primary={item.value} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
Appreciate any help.
I worked before on MUI.
You need to add one more parameter to the handleChange function to access ID. But it is currently returning ID with .$. So you have to filter these two chars.
const handleChange = (event,obj) => {
const {
target: { value }
} = event;
setSelected(typeof value === "string" ? value.split(",") : value);
// on selecting first option, will return
// .$8271e42a-8982-44b8-9745-3271e0cf9d12
console.log(obj.key);
};

Ant design Transform component extracting selected data

I need to extract selected/filtered data into the state. I tried everything with onChange and on select change but only I got is the ID of one specific item.
ex. extracted_data = [ '1','2','3'....]
But I need a complete Item with id, name, price, etc...
I am passing data as source data, and after selecting I want to send in the parent component because I need to send it on the server.
Code is below
import { Table, Transfer } from "antd";
import difference from "lodash/difference";
import React, { useState } from "react";
// Customize Table Transfer
const TableTransfer = ({ leftColumns, rightColumns, ...restProps }) => (
<Transfer {...restProps}>
{({
direction,
filteredItems,
onItemSelectAll,
onItemSelect,
selectedKeys: listSelectedKeys,
disabled: listDisabled,
}) => {
const columns = direction === "left" ? leftColumns : rightColumns;
const rowSelection = {
getCheckboxProps: (item) => ({
disabled: listDisabled || item.disabled,
}),
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows
.filter((item) => !item.disabled)
.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, listSelectedKeys)
: difference(listSelectedKeys, treeSelectedKeys);
onItemSelectAll(diffKeys, selected);
},
onSelect({ key }, selected) {
onItemSelect(key, selected);
},
selectedRowKeys: listSelectedKeys,
};
return (
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={filteredItems}
size="small"
style={{
pointerEvents: listDisabled ? "none" : undefined,
}}
onRow={({ key, disabled: itemDisabled }) => ({
onClick: () => {
if (itemDisabled || listDisabled) return;
onItemSelect(key, !listSelectedKeys.includes(key));
},
})}
/>
);
}}
</Transfer>
);
const leftTableColumns = [
{
dataIndex: "name",
title: "Name",
},
{
dataIndex: "price",
title: "Price (€)",
},
{
dataIndex: "discount",
title: "Discount (%)",
},
];
const rightTableColumns = [
{
dataIndex: "name",
title: "Name",
},
{
dataIndex: "price",
title: "Price",
},
];
const App = ({ data, func }) => {
const mockData = data.map((item) => ({
key: item.id.toString(),
name: item.name,
price: item.price,
discount: item.discount,
}));
const originTargetKeys = mockData.map((item) => item.key);
const [targetKeys, setTargetKeys] = useState(originTargetKeys);
const [selected, setSelected] = useState([]);
const onSelectChange = (e) => {
setSelected(e);
};
const onChange = (e) => {
setTargetKeys(e);
func(selected);
};
return (
<>
<TableTransfer
dataSource={mockData}
targetKeys={targetKeys}
disabled={false}
showSearch={true}
onChange={onChange}
onSelectChange={onSelectChange}
filterOption={(inputValue, item) =>
item.name.indexOf(inputValue) !== -1
}
leftColumns={leftTableColumns}
rightColumns={rightTableColumns}
/>
</>
);
};
export default App;

React Filter and Map data for Datagrid returning no values

I suspect it is a syntax issue. When I use the same approach for instead of I do get the expected results. The data is coming from an endpoint through the import of getAssembly which is generated via await fetch and the results are being rendered as JSON before the data is imported.
The commented out code on line how I assume the commanded need to be executed and I get no error, but no data is rendered. The code on line 50 works fine but does not provide the filtering. I intend the use the value in the typeName variable once the filtering is working.
import { useState, useEffect } from "react";
import MenuItem from "#mui/material/MenuItem";
import Select from "#mui/material/Select";
import InputLabel from "#mui/material/InputLabel";
import ListItemText from "#mui/material/ListItemText";
import { DataGrid } from "#mui/x-data-grid";
import { getAssembly } from "./GetAssembly";
const columns = [
{ field: "id", headerName: "ID", width: 300 },
{ field: "status", headerName: "Status", width: 90 },
{ field: "atype", headerName: "AType", width: 80 },
{ field: "name", headerName: "Name", width: 350 },
{ field: "time", headerName: "Start Time", width: 150 },
{ field: "org", headerName: "Organization", width: 350 },
];
export default function SelectAssembly() {
const [typeName, setTypeName] = useState([""]);
// const [assemRows, setAssemRows] = useState([]);
const [state, setState] = useState({
assembly: [],
assembtypename: [],
unsignenList: [],
});
const handleTypeChange = (event) => {
const {
target: { value },
} = event;
setTypeName(value);
};
console.log(typeName);
useEffect(() => {
console.log("useEffect");
getAssembly().then((res) => {
setState((prevState) => ({ ...prevState, assembly: res.assemblies }));
});
}, []);
const typeSelection = [
...new Set(state.assembly.map((item) => item.assemblyType)),
];
//const assemList = state.assembly.filter(assem => assem === "batch").map(assem => {
const assemList = state.assembly.map((assem) => {
return {
id: assem.assemblyId,
status: assem.status,
atype: assem.assemblyType,
name: assem.name,
time: assem.timeStarted,
org: assem.organizationId,
asid: assem.referenceId,
pmap: assem.propertiesMap,
};
});
// const unsignedList = assemList.filter((str) => {
// //str can include or str can equal with === (return str.includes("import");)
// return str === "import";
// });
return (
<div>
<InputLabel sx={{ fontSize: 12 }}>Assembly Type</InputLabel>
<Select
label="Type"
value={typeName}
sx={{ height: 35, fontSize: 10 }}
fullWidth
focused
onChange={handleTypeChange}
>
{typeSelection.map((types) => {
return (
<MenuItem key={types.indexOf(types) > -1} value={types}>
<ListItemText primary={types} />
</MenuItem>
);
})}
</Select>
<br />
<br />
<DataGrid
density="compact"
hideFooterRowCount
rows={assemList}
// rows={unsignedList}
columns={columns}
pageSize={15}
rowsPerPageOptions={[15]}
/>
</div>
);
}
Thanks to Jim Ptak at Southwire for helping me see the problem. In the filter method I did not specify the particular element to filter on. Once the code was modified as follows:
const assemList = state.assembly.filter(assem => assem.assemblyType === "MO import").map(assem => {
//const assemList = state.assembly.map((assem) => {
return {
id: assem.assemblyId,
status: assem.status,
atype: assem.assemblyType,
name: assem.name,
time: assem.timeStarted,
org: assem.organizationId,
asid: assem.referenceId,
pmap: assem.propertiesMap,
};
});
the contents of the datagrid filters perfectly.

How to delete a row from a DataGrid that is connected to Firebase?

I have a DataGrid table with data that comes from Firebase and I wanted to know how can I delete and update the firebase information ?
I have this piece of code that deletes the row and it does works BUT because I haven't add anything to update the firebase it will not delete it permanently (which makes perfect sense):
Edit: Deleted unnecessary piece of code to just leave delete function
this is just after a row has been check then it let's you delete that checked row (and it works) but I don't see space (it brings out compile errors) to add the firebase delete() function in that piece of code.
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
setEstudiantes((r) =>
r.filter((x) =>
!selectedIDs.has(x.id)
));
}}
>
<DeleteOutlinedIcon />
</IconButton>
This is how I do the check of the rows (and it does work):
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange = {(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) =>
selectedIDs.has(row.id)
);
setEstudiantesData(selectedRowData)
}
}
{...estudiantes}
However I do have the delete function that connects with my firebase and deletes documents that I did before migrating to MUI DataGrid but I do not know how to integrated it. This is how you delete something in firebase usually
db.collection("usuarios")
.doc(user.uid)
.collection("estudiantes")
.doc(document name variable)
.delete();
Thank you any tip/help is welcome.
*UPDATE this is how it looks
it does the delete as intended but it doesn't update the firebase and idk where to add the code that does that because w/e I try to add it it comes out as an error:
if I just refresh it comes back:
UPDATE Adding the code of the DataGrid:
return (
<Container fixed>
<Box mb={5} pt={2} sx={{textAlign:'center'}}>
<Button
startIcon = {<PersonAddIcon />}
variant = "contained"
color = "primary"
size = "medium"
onClick={crearEstudiante} >
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{height: '390px', width: "850px", textAlign:'center'}}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange = {(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) =>
selectedIDs.has(row.id)
);
setEstudiantesData(selectedRowData)
}
}
{...estudiantes}
/>
</Box>
<Button
startIcon = {<ShoppingCartSharpIcon />}
variant = "contained"
color = "primary"
size = "medium"
onClick={realizarPedidos} >
Crear pedido
</Button>
</Box></Container>
)
Update Adding picture of the error I get when I try to add the logic to update the firebase, doesn't matter where I put it inside the delete logic it just gives me an error, I honestly do not know where to put it since I don't understand very well the selection of MUI on DataGrid
Update Adding my whole code:
import React, { useState, useEffect} from 'react'
import {db} from './firebase';
import { useHistory } from 'react-router-dom';
import "./ListadoEstudiantes.css"
import { DataGrid,
GridToolbarContainer, GridToolbarFilterButton, GridToolbarDensitySelector} from '#mui/x-data-grid';
import { Button, Container } from "#material-ui/core";
import { IconButton} from '#mui/material';
import PersonAddIcon from '#mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '#mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined';
import { Box } from '#mui/system';
function ListadoEstudiantes({user}) {
const history = useHistory("");
const crearEstudiante = () => {
history.push("/Crear_Estudiante");
};
const [estudiantesData, setEstudiantesData] = useState([])
const parseData = {
pathname: '/Crear_Pedidos',
data: estudiantesData
}
const realizarPedidos = () => {
if(estudiantesData == 0)
{
window.alert("Seleccione al menos un estudiante")
}
else {
history.push(parseData);
}
};
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</GridToolbarContainer>
);
}
const [estudiantes, setEstudiantes] = useState([]);
const [selectionModel, setSelectionModel] = useState([]);
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{field: 'nombre', headerName: 'Nombre', width: 200},
{field: 'colegio', headerName: 'Colegio', width: 250},
{field: 'grado', headerName: 'Grado', width: 150},
{
field: "delete",
width: 75,
sortable: false,
disableColumnMenu: true,
renderHeader: () => {
return (
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
setEstudiantes((r) =>
r.filter((x) =>
!selectedIDs.has(x.id)
));
}}
>
<DeleteOutlinedIcon />
</IconButton>
);
}
}
];
const deleteProduct = (estudiante) => {
if (window.confirm('Quiere borrar este estudiante ?')){
db.collection("usuarios").doc(user.uid).collection("estudiantes").doc(estudiante).delete();
}
}
useEffect(() => {
}, [estudiantesData])
const estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
useEffect(() => {
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes)
})
}, []);
useEffect (() => {
const estData = window.localStorage.getItem("estudiantes");
setEstudiantes(JSON.parse(estData))
}, [])
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
})
return (
<Container fixed>
<Box mb={5} pt={2} sx={{textAlign:'center'}}>
<Button
startIcon = {<PersonAddIcon />}
variant = "contained"
color = "primary"
size = "medium"
onClick={crearEstudiante} >
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{height: '390px', width: "850px", textAlign:'center'}}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange = {(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) =>
selectedIDs.has(row.id)
);
setEstudiantesData(selectedRowData)
}
}
{...estudiantes}
/>
</Box>
<Button
startIcon = {<ShoppingCartSharpIcon />}
variant = "contained"
color = "primary"
size = "medium"
onClick={realizarPedidos} >
Crear pedido
</Button>
</Box></Container>
)
}
export default ListadoEstudiantes
So after you filter estudiantes, you're left with the items that the user does not want to delete. But before we do that, we're going to have to get the items that the user wants to delete so we can delete them from Firebase.
You could replace the onClick function of the delete button with:
onClick={() => {
const selectedIDs = new Set(selectionModel);
estudiantes.filter((x) =>
selectedIDs.has(x.id)).map( x => {
db.collection("usuarios").doc(user.uid).collection("estudiantes").doc(x.uid).delete()
})
)
////If NOT updating from db add this
setEstudiantes(estudiantes.filter((x) =>
!selectedIDs.has(x.id)))
}}
Items that the user wants to delete are in selectedIDs so we need to use selectedIDs.has(x.id) instead of !selectedIDs.has(x.id). Now we're left with an array that includes only the items for deletion. We map this array to delete each one of these selected items. By using the map method.
After this, you can filter estudiantes since now we don't need the items we just deleted from Firebase. (you can skip this one if you're updating from firebase as the new data won't include deleted items).
Please let me know if this is what you were looking for.

Display data in antd table based on selected item from select option

I need to display data on the antd table based on the selected item from select option.The data that are to be displayed are stored in different variables. For example, if school is selected from select option then the datasource is available in schoolData and similarly for other option.
Here's my code:
import React, { useState } from "react";
import Framework from "../framework/Framework";
import { Dropdown, Button, Table, message, Select } from "antd";
import { DeleteOutlined, DownOutlined, EditOutlined } from "#ant-design/icons";
import { Content } from "antd/lib/layout/layout";
import AddNewButton from "../addNewButton/AddNewButton";
import "./attributes.css";
import DataSource from "./Datasource";
import IconDescription from "../icondescription/IconDescription";
import Modal from "antd/lib/modal/Modal";
import { Option } from "antd/lib/mentions";
const Attributes = () => {
const [page, setPage] = useState(1);
const [isModalVisible, setIsModalVisible] = useState(false)
// const [layer, setLayer] = useState()
const columns = [
{
title: "S.N",
dataIndex: "key",
},
{
title: "Name",
dataIndex: "name",
key: "name",
// render: (text) => <a>{text}</a>,
},
{
title: "Address",
dataIndex: "address",
key: "address",
},
{
title: "Contact No.",
dataIndex: "contactno",
key: "contactno",
},
{
title: "Operation",
dataIndex: "operation",
key: "operation",
render: () => {
return (
<div style={{ display: "flex" }}>
<IconDescription icon={<EditOutlined />} label="Edit" />
<IconDescription icon={<DeleteOutlined />} label="Delete" />
</div>
);
},
},
];
const addAttribute = () => {
setIsModalVisible(true)
}
const modalHandleOk = () => {
setIsModalVisible(false);
};
const modalHandleCancel = () => {
setIsModalVisible(false);
};
const selectLayer = (e) => {
console.log("select layer", e)
}
return (
<Framework>
<Content className="attributes">
<div className="select-addNewBtn-container">
<Select defaultValue="school" style={{ width: 120 }} onChange={selectLayer}>
<Option value="school">School</Option>
<Option value="hospital">Hospital</Option>
<Option value="policeStation">Police Station</Option>
</Select>
<AddNewButton name={"Add New Attribute"} addNewBtn={addAttribute} />
<Modal title={"Add New Attribute"} visible={isModalVisible} centered onCancel={modalHandleCancel} onOk={modalHandleOk}>
</Modal>
</div>
<Table
dataSource={DataSource}
columns={columns}
className="data-table"
pagination={{
size: "small",
pageSize: 6,
hideOnSinglePage: true,
showSizeChanger: false,
}}
/>
</Content>
</Framework>
);
};
export default Attributes;
How do i achieve the desired functionality? Do let me know. Quite a beginner at such things.
Assuming Datasource is just a simple array of data objects, and presumably you have another file eg: 'SchoolData' that you need to switch between when the select option is chosen.
What I'd do to keep it simple is create a react state variable to wrap your data and just set it at will in the onChange of your select.
Example

Resources