State not updating on Drag and Drop Formik Component - reactjs

I'm working on a family tree where children can be moved to other parents. Issue comes when I reorder a family on drag and drop(DnD), it doesn't update in the formik values object. Also when I delete a child on the DnD it doesn't update the DnD objects but updates the formik values objects.
Codesandbox link is: https://codesandbox.io/s/kind-hermann-lqve6
Below is the code:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Formik, FieldArray } from "formik";
import { Button, Form } from "antd";
import { DndProvider } from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend";
import Sortly, { ContextProvider, useDrag, useDrop } from "react-sortly";
const FormItem = Form.Item;
const ItemRenderer = props => {
const {
index,
data: { text, kids }
} = props;
const [, drag] = useDrag();
const [, drop] = useDrop();
return (
<FieldArray
name="family.parents"
render={arrayHelpers => (
<div ref={drop} style={{ width: "40vw" }} key={index}>
<div
ref={drag}
style={{
padding: "0.5rem 1rem",
marginBottom: ".5rem",
backgroundColor: "green",
cursor: "move",
color: "white",
width: "320px"
}}
>
{`${text}`}
<FormItem style={{ display: "inline-block" }}>
<span className="parent-utility">
<Button
className="ant-btn"
shape="circle"
size="small"
style={{ color: "#ffa500" }}
onClick={() => arrayHelpers.remove(index)}
>
Delete
</Button>
</span>
</FormItem>
</div>
<KSortableTree kidsTree={kids} position={index} />
</div>
)}
/>
);
};
const ItemRendererA = props => {
const {
index,
position,
data: { text }
} = props;
const [, drag] = useDrag();
const [, drop] = useDrop();
return (
<FieldArray
name={`family.parents[${position}].kids`}
render={arrayHelpers => (
<>
<div ref={drop} style={{ width: "40vw" }}>
<div
ref={drag}
style={{
border: "1px dashed #70B5DC",
padding: "0.5rem 1rem",
marginBottom: ".5rem",
marginLeft: "3rem",
backgroundColor: "white",
cursor: "move",
color: "#70B5DC",
width: "17rem"
}}
>
{`${text}`}
<FormItem style={{ display: "inline-block" }}>
<span className="answer-utility">
<Button
className="ant-btn"
shape="circle"
size="small"
style={{ color: "#ffa500" }}
onClick={() => {
arrayHelpers.remove(index);
}}
>
Delete
</Button>
</span>
</FormItem>
</div>
</div>
</>
)}
/>
);
};
const PSortableTree = parentTree => {
const [items, setItems] = useState(parentTree.parentTree);
const handleChange = newItems => {
setItems(newItems);
};
return (
<Sortly items={items} onChange={handleChange}>
{props => <ItemRenderer {...props} />}
</Sortly>
);
};
const KSortableTree = kidsTree => {
const [itemsA, setItemsA] = useState(kidsTree.kidsTree);
const [positionA] = useState(kidsTree.position);
const handleChangeA = newItemsA => {
setItemsA(newItemsA);
};
return (
<Sortly items={itemsA} onChange={handleChangeA}>
{props => <ItemRendererA {...props} position={positionA} />}
</Sortly>
);
};
function FormDetail({ values, handleSubmit }) {
return (
<form onSubmit={handleSubmit}>
<h3>Family Tree</h3>
<DndProvider backend={HTML5Backend}>
<ContextProvider>
<PSortableTree parentTree={values.family.parents} />
</ContextProvider>
</DndProvider>
<div
style={{
padding: 16,
backgroundColor: "lightgrey",
borderRadius: "8px",
marginTop: "20px",
maxWidth: "100vw"
}}
>
{JSON.stringify(values)}
</div>
</form>
);
}
function App() {
return (
<Formik
enableReinitialize
initialValues={{
family: {
id: "2da80396",
parents: [
{
depth: 0,
id: "01a3b77c",
kids: [
{
depth: "1",
id: "01a3b77d",
text: "A",
correct: true
},
{
depth: "1",
id: "02fd6f62",
text: "C",
correct: false
}
],
text: "Parent 1"
},
{
depth: 0,
id: "773a4170",
kids: [
{
depth: "1",
id: "773a4171",
text: "A",
correct: false
},
{
depth: "1",
id: "773a4172",
text: "B",
correct: true
},
{
depth: "1",
id: "773a4173",
text: "C",
correct: false
}
],
text: "Parent 2"
},
{
depth: 0,
id: "a3652131",
kids: [
{
depth: "1",
id: "1c081c33",
text: "G",
correct: false
},
{
depth: "1",
id: "a3654842",
text: "A",
correct: true
},
{
depth: "1",
id: "a3654843",
text: "B",
correct: false
}
],
text: "Parent 3"
},
{
depth: 0,
id: "a3654845",
kids: [
{
depth: "1",
id: "a3654846",
text: "A",
correct: true
},
{
depth: "1",
id: "a3654847",
text: "B",
correct: false
}
],
text: "Parent 4"
},
{
depth: 0,
id: "eb3dc4d9",
kids: [
{
depth: "1",
id: "2103b3bc",
text: "D",
correct: true
},
{
depth: "1",
id: "28650f35",
text: "A",
correct: false
},
{
depth: "1",
id: "699584c2",
text: "B",
correct: false
},
{
depth: "1",
id: "a9edd5c4",
text: "C",
correct: false
}
],
text: "Parent 5"
}
],
text: "Heading"
}
}}
onSubmit={values => alert(JSON.stringify(values))}
component={FormDetail}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

React component not passing value to correct component

I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.
The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from #carbon/react) doesn't work as desired.
I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.
The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js
This is the current code:
ColorPicker2.js
import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";
const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];
const ColorPicker2 = (props) => {
const handleChangeComplete = (color) => {
if (props.updateColor) {
props.updateColor(color.hex);
}
};
return (
<div className="palette">
<BlockPicker
className="palette"
colors={presetColors}
onChangeComplete={handleChangeComplete}
presetColors={Object.values(presetColors)}
color={props.currentColor}
/>
</div>
);
};
export default ColorPicker2;
App.js
import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "#carbon/react";
const DndWrapper = (props) => {
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};
const DraggableSquare = ({ index, text, color, moveSquare }) => {
const [{ isDragging }, drag] = useDrag({
type: "square",
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [isHovered, setIsHovered] = useState(false);
const [, drop2] = useDrop({
accept: "square",
drop: (item, monitor) => {
const didDrop = monitor.didDrop();
if (!didDrop) {
moveSquare(item.index, index);
}
},
hover: (item, monitor) => {
setIsHovered(monitor.isOver());
},
collect: (monitor) => {
setIsHovered(monitor.isOver());
}
});
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [isTextInputOpen, setIsTextInputOpen] = useState(false);
const [newText, setNewText] = useState(text);
const opacity = isDragging ? 0.5 : 1;
return (
<div className="square-div" ref={drop2}>
<div
className="grey-square"
ref={drag}
style={{
opacity,
backgroundColor: color,
width: "200px",
height: "200px",
textAlign: "center",
paddingTop: "30px",
position: "relative",
border: isHovered ? "3px solid blue" : "none",
borderRadius: "5px",
}}
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={edit}
onClick={() => {
setIsTextInputOpen(!isTextInputOpen);
if (!isTextInputOpen) {
moveSquare(index, newText, color, undefined);
}
}}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "5px",
top: "5px"
}}
alt="edit icon"
/>
{isTextInputOpen && (
<TextInput
id="newtext"
labelText=""
value={newText}
onChange={(e) => setNewText(e.target.value)}
/>
)}
<img
src={palette}
onClick={() => setIsPaletteOpen(!isPaletteOpen)}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "25px",
top: "5px"
}}
alt="palette icon"
/>
{isPaletteOpen && (
<ColorPicker2
className="palette"
currentColor={color}
updateColor={(newColor) =>
moveSquare(index, index, newText, newColor)
}
/>
)}
</div>
</div>
);
};
const Grid = () => {
const [grid, setGrid] = useState([
{ text: "1", color: "grey" },
{ text: "2", color: "grey" },
{ text: "3", color: "grey" },
{ text: "4", color: "grey" },
{ text: "5", color: "grey" },
{ text: "6", color: "grey" },
{ text: "7", color: "grey" },
{ text: "8", color: "grey" },
{ text: "9", color: "grey" },
{ text: "10", color: "grey" },
{ text: "11", color: "grey" },
{ text: "12", color: "grey" },
{ text: "13", color: "grey" },
{ text: "14", color: "grey" },
{ text: "15", color: "grey" }
]);
const moveSquare = (fromIndex, toIndex, newText, newColor) => {
setGrid((grid) => {
const newGrid = [...grid];
const item = newGrid[fromIndex];
newGrid.splice(fromIndex, 1);
newGrid.splice(toIndex, 0, {
text: newText || item.text,
color: newColor || item.color
});
return newGrid;
});
};
return (
<>
<DndWrapper>
<div
className="grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(5, 190px)",
gridGap: "15px",
gridColumnGap: "20px",
gridRowGap: "10px",
position: "absolute"
}}
>
{grid.map((square, index) => (
<DraggableSquare
key={index}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>
))}
</div>
</DndWrapper>
</>
);
};
export default Grid;
Any thoughts from fresh eyes would be helpful.
I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.
Unique Id in objects:
const [grid, setGrid] = useState([
{ text: "1", color: "grey", id: crypto.randomUUID() },
{ text: "2", color: "grey", id: crypto.randomUUID() },
{ text: "3", color: "grey", id: crypto.randomUUID() },...])
Adding key to mapped object:
{grid.map((square, index) => (
<DraggableSquare
key={square.id}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>}

How to get values of some form fields in react by clicking on a button?

I've created a form using antd-form-builder which contains 3 parts. In "Routines" part user can add several "Tasks" I need to collect each task information by clicking on the "Add Routine" button in an array like this:
"routinesList": [
{
"routinType": 1,
"intervalDays": 20,
"nextDueDate": "2023-01-08T15:46:33.185Z",
"kindOfDate": 1,
"serviceProvider": "string",
"sop": "string",
"displayBeforeDue": 1,
"lastDate": "2023-01-08T15:46:33.185Z",
"nextDate": "2023-01-08T15:46:33.185Z",
"taskId": 1
}
]
and finally send all 3 parts data to the server by clicking on submit button.
how can I do it?
here is my code for Routine part of the form:
import React, { Fragment, useEffect, useState, useCallback } from "react"
import { Form, List, Select, Input, InputNumber, Row, Col, Button } from "antd"
import FormBuilder from "antd-form-builder"
import { MinusCircleOutlined, PlusOutlined } from "#ant-design/icons"
import axios from "axios"
const Option = Select.Option
const IntervalInput = ({ value, onChange }) => (
<Row gutter={10}>
<Col span={16}>
<InputNumber min={1} style={{ width: "100%" }} value={value.number} onChange={(v) => onChange({ number: v, unit: value.unit })} />
</Col>
<Col span={8}>
<Select value={value.unit} onChange={(v) => onChange({ number: value.number, unit: v })}>
<Option value="1">Day(s)</Option>
<Option value="2">Month(s)</Option>
</Select>
</Col>
</Row>
)
function RoutineInfo(props) {
const [form] = Form.useForm()
const [routineData, setRoutineData] = useState([])
const [opts, setOpts] = useState({ sop: [], service: [] })
useEffect(() => {
const fetchOpts = async () => {
const serviceData = await axios("api?id=3")
setOpts({ sop: [], service: serviceData.data })
}
fetchOpts()
}, [])
const optsService = opts.service
const serviceOptions = optsService.map(({ typeValue }) => typeValue)
const columns = 2
const meta = {
columns,
fields: [
{
key: "routineType",
label: "Routine Type",
widget: "select",
options: [
{ label: "Calibration", value: 1 },
{ label: "Maintenance", value: 2 },
],
initialValue: 1,
colSpan: 2,
},
{
key: "interval",
label: "Interval",
forwardRef: true,
widget: IntervalInput,
initialValue: { number: 30, unit: "Days" },
},
{
key: "lastDate",
label: "Last Due Date",
widget: "date-picker",
widgetProps: { style: { width: "100%" } },
colSpan: 2,
},
{
key: "nextDate",
label: "Next Due Date",
widget: "date-picker",
widgetProps: { style: { width: "100%" } },
colSpan: 2,
},
{
key: "displayBeforeDue",
label: "Display Before Due",
widget: "number",
colSpan: 2,
},
{
key: "sop",
label: "SOP",
widget: "select",
colSpan: 2,
},
{
key: "serviceProvider",
label: "Service Provider",
widget: "select",
//onChange: (e) => setRoutineData(e.target.value),
options: serviceOptions,
widgetProps: { showSearch: true },
colSpan: 2,
},
{
key: "task",
label: "Task",
//forwardRef: true,
widget: "select",
widgetProps: { showSearch: true },
options: [
{ label: "Pre-filter Replacement", value: 1 },
{ label: "Oil Change", value: 2 },
],
colSpan: 2,
},
],
}
const getData = () => {
console.log(routineData)
}
return (
<Fragment>
<fieldset className="form-frame">
<FormBuilder meta={meta} form={form} />
<Form.Item className="ant-form-item--btn">
<Button type="primary" onClick={getData}>
Add Routine
</Button>
</Form.Item>
</fieldset>
<div
id="scrollableDiv"
style={{
//height: 272,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
// here I want to display tasks in a list
<List
itemLayout="horizontal"
//dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`api/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
<Form.Item className="ant-form-item--btn">
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Fragment>
)
}
export default RoutineInfo

How to handle onChange and onClick event used in each row of Mui Table

How can I populate same type of dropdown and button in each row of table? I am using reusable table component to populate table data but how to manage handleChange and handleClick for each row separately.
CodeSandBox link: https://codesandbox.io/s/stupefied-feynman-6mdj77?file=/demo.js:0-390
Reusable table
tableComponent.jsx :
import * as React from "react";
import { Table, TableBody, TableHead, Paper, Box } from "#mui/material";
import { StyledTableCell, StyledTableRow, StyledContainer } from "./tableStyle";
export default function CustomTable({ cols, data }) {
console.log("CustomTable", cols, data);
return (
<Paper>
<StyledContainer>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<StyledTableRow>
{cols.map((headerItem, index) => (
<StyledTableCell key={index}>
<Box sx={{ textAlign: "center", fontSize: "14px" }}>
{headerItem.title}
</Box>
</StyledTableCell>
))}
</StyledTableRow>
</TableHead>
<TableBody>
{data.map((item, index) => (
<StyledTableRow key={index}>
{cols.map((col, key) => (
<StyledTableCell key={key}>
{col.render(item)}
</StyledTableCell>
))}
</StyledTableRow>
))}
</TableBody>
</Table>
</StyledContainer>
</Paper>
);
}
tableConstant.js :
import React from "react";
import { Box, MenuItem, Select, Button } from "#mui/material";
export const TableConstants = (handleChange, handleClick) => {
const [name, setName] = React.useState("");
return [
{
title: "No.",
render: (rowData) => {
return (
<Box
sx={{
display: "flex",
alignItem: "center",
justifyContent: "center"
}}
>
{rowData.id}
</Box>
);
}
},
{
title: "Manage",
render: () => {
return (
<Box
sx={{
display: "flex",
alignItem: "center",
justifyContent: "center"
}}
>
{" "}
<Select
// onChange={}
fullWidth
size="small"
MenuProps={{ MenuListProps: { disablePadding: true } }}
sx={{
fontSize: "14px",
width: "100px",
height: "28px"
}}
>
<MenuItem
sx={{
height: "25px",
width: "100%"
}}
value="Allow"
>
Allow
</MenuItem>
<MenuItem
sx={{
height: "30px",
width: "100%"
}}
value="Decline"
>
Decline
</MenuItem>
</Select>
</Box>
);
}
},
{
title: "Name",
render: (rowData) => {
return <Box>{rowData.name}</Box>;
}
},
{
title: "Designation",
render: (rowData) => {
return <Box>{rowData.designation}</Box>;
}
},
{
title: "Email",
render: (rowData) => {
return <Box>{rowData.email}</Box>;
}
},
{
title: "Mobile",
render: (rowData) => {
return <Box>{rowData.mobile}</Box>;
}
},
{
title: "Access",
render: () => {
return (
<Box
sx={{
display: "flex",
alignItem: "center",
justifyContent: "center"
}}
>
<Button
// onClick={}
>
{" "}
view
</Button>
</Box>
);
}
}
];
};
data.js:
export const data = [
{
id: 1,
name: "Leanne Graham",
designation: "Tester",
email: "Sincere#april.biz",
mobile: "1-770-736-8031 x56442"
},
{
id: 2,
name: "Ervin Howell",
designation: "Developer",
email: "Shanna#melissa.tv",
mobile: "010-692-6593 x09125"
},
{
id: 3,
name: "Clementine Bauch",
designation: "Designer",
email: "Nathan#yesenia.net",
mobile: "1-463-123-4447"
},
{
id: 4,
name: "Patricia Lebsack",
designation: "Analyst",
email: "Julianne.OConner#kory.org",
mobile: "493-170-9623 x156"
},
{
id: 5,
name: "Chelsey Dietrich",
designation: "Deta Engineer",
email: "Lucio_Hettinger#annie.ca",
mobile: "(254)954-1289"
}
];
Final Table :
import React from "react";
import Table from "./table";
import { data } from "./data";
import { TableConstants } from "./tableConstant";
const handleChange = (item) => {
alert(JSON.stringify(item));
};
const ManageAccess = () => {
return (
<React.Fragment>
<Table cols={TableConstants(handleChange)} data={data} />
</React.Fragment>
);
};
export default ManageAccess;

conditionally rendered Nested submenu not closed if click item on more than one level

If I don't render the submenu conditionally and if I click the menu item in two or three level it closes all the opened submenu because it depends on hover, so this is working perfectly here. but if I render the submenu conditionally with "isOpen" state and if I click the menu item on two three level it only closes the current submenu not all the opened submenus.
import React from "react";
const menus = [
{
title: "Home",
},
{
title: "Contact",
submenus: [
{
title: "Phone",
},
{
title: "Mobile",
submenus: [
{
title: "National",
},
{
title: "International",
submenus: [
{
title: "Earth",
},
{
title: "Mars",
},
],
},
],
},
{
title: "SEO",
},
],
},
{
title: "About",
submenus: [
{
title: "Blog",
},
{
title: "Vlog",
},
],
},
];
const SubMenu = ({ submenus, isOpen, onClick, depthLevel, ...rest }) => {
const style = {
listStyle: "none",
display: isOpen ? "block" : "none",
border: "2px solid red",
position: "absolute",
top: depthLevel > 0 ? "0" : "100%",
left: depthLevel > 0 ? "100%" : "0",
border: "1px solid #ddd",
};
return (
<ul style={style} {...rest}>
{submenus.map((submenu, index) => (
<MenuItem
{...submenu}
key={index}
onClick={onClick}
depthLevel={depthLevel + 1}
/>
))}
</ul>
);
};
const MenuItem = ({ title, submenus, onClick, depthLevel }) => {
const [isOpen, seOpen] = React.useState(false);
const handleMouseEnter = () => {
seOpen(true);
};
const handleMouseLeave = () => {
seOpen(false);
};
const handleClick = () => {
seOpen(false);
};
const style = {
position: "relative",
};
return (
<li style={style}>
{submenus ? (
<React.Fragment>
<button
type="button"
aria-haspopup="menu"
aria-expanded={isOpen ? "true" : "false"}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{title}
</button>
<SubMenu
depthLevel={depthLevel}
submenus={submenus}
isOpen={isOpen}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
/>
</React.Fragment>
) : (
<a href="#" onClick={onClick}>
{title}
</a>
)}
</li>
);
};
export default function Menu() {
const style = {
listStyle: "none",
display: "flex",
};
return (
<ul style={style}>
{menus.map((menu, index) => (
<MenuItem {...menu} key={index} depthLevel={0} />
))}
</ul>
);
}
It works perfectly fine here.
But if i render the submenu conditionally like this.
import React from "react";
const menus = [
{
title: "Home",
},
{
title: "Contact",
submenus: [
{
title: "Phone",
},
{
title: "Mobile",
submenus: [
{
title: "National",
},
{
title: "International",
submenus: [
{
title: "Earth",
},
{
title: "Mars",
},
],
},
],
},
{
title: "SEO",
},
],
},
{
title: "About",
submenus: [
{
title: "Blog",
},
{
title: "Vlog",
},
],
},
];
const SubMenu = ({ submenus, isOpen, onClick, depthLevel, ...rest }) => {
const style = {
listStyle: "none",
border: "2px solid red",
position: "absolute",
top: depthLevel > 0 ? "0" : "100%",
left: depthLevel > 0 ? "100%" : "0",
border: "1px solid #ddd",
};
return (
isOpen && (
<ul style={style} {...rest}>
{submenus.map((submenu, index) => (
<MenuItem
{...submenu}
key={index}
onClick={onClick}
depthLevel={depthLevel + 1}
/>
))}
</ul>
)
);
};
const MenuItem = ({ title, submenus, onClick, depthLevel }) => {
const [isOpen, seOpen] = React.useState(false);
const handleMouseEnter = () => {
seOpen(true);
};
const handleMouseLeave = () => {
seOpen(false);
};
const handleClick = () => {
seOpen(false);
};
const style = {
position: "relative",
};
return (
<li style={style}>
{submenus ? (
<React.Fragment>
<button
type="button"
aria-haspopup="menu"
aria-expanded={isOpen ? "true" : "false"}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{title}
</button>
<SubMenu
depthLevel={depthLevel}
submenus={submenus}
isOpen={isOpen}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
/>
</React.Fragment>
) : (
<a href="#" onClick={onClick}>
{title}
</a>
)}
</li>
);
};
export default function ConditionallyRenderMenu() {
const style = {
listStyle: "none",
display: "flex",
};
return (
<ul style={style}>
{menus.map((menu, index) => (
<MenuItem {...menu} key={index} depthLevel={0} />
))}
</ul>
);
}
It doesn't close all the submenu. It only closes the respected one.

Material Table rowStyle background color change based on cell value in

I am trying to have the color of the row change colors based on the priority number. For example, if the priority is 4 the color of the entire row should be blue. The problem is that I'm unsure of how to achieve this. I know Material Table assigns ID's to the rows, but I have no way of accessing them. Below is what I have come up with so far. I need options[backgroundColor] to be set based on the priority number. I have a codesandbox here as well https://codesandbox.io/s/notifications-material-ui-spq45
import React from "react";
import { Container } from "react-bootstrap";
import MaterialTable from "material-table";
import FilterListIcon from "#material-ui/icons/FilterList";
import SearchIcon from "#material-ui/icons/Search";
import FirstPage from "#material-ui/icons/FirstPage";
import LastPage from "#material-ui/icons/LastPage";
import ChevronLeft from "#material-ui/icons/ChevronLeft";
import ChevronRight from "#material-ui/icons/ChevronRight";
import CloseIcon from "#material-ui/icons/Close";
function App() {
//loop through the array of priority numbers, and display color based on number
function colors(num) {
for(let i = 0; i < num.length; i++) {
if (num[i] === 4) {
options.rowStyle.backgroundColor = "blue";
}
if (num[i] === 3) {
options.rowStyle.backgroundColor = "red";
}
console.log(num[i]);
}
}
let data = [
{
date: "06-29-2021",
subject: "CANBUS LOSS, Automatic Reboot!",
message:
"SCMs CANBUS LOSS for more than 15 min, Automatic System Reboot!",
category: "System",
priority: 4,
error: "SYS0080"
},
{
date: "06-28-2021",
subject: "Reboot!",
message: "Automatic System Reboot!",
category: "Alarm",
priority: 3,
error: "SYS0090"
},
{
date: "06-25-2021",
subject: "Testing!",
message: "Generator not running!",
category: "Generator",
priority: 2,
error: "SYS0050"
}
];
let columns = [
{ title: "Date", field: "date" },
{ title: "Subject", field: "subject" },
{ title: "Message", field: "message" },
{ title: "Category", field: "category" },
{ title: "Priority Level", field: "priority" },
{ title: "Error Code", field: "error" }
];
let options = {
filtering: false,
sorting: true,
rowStyle: {
fontFamily: "Mulish-Regular",
backgroundColor: ""
},
headerStyle: {
fontFamily: "Mulish-Regular",
fontSize: "1.1em",
fontWeight: "600",
backgroundColor: "#D1D1D8"
},
searchFieldStyle: {
fontFamily: "Mulish-Regular"
}
};
// Loop through all the data and find the priority number, and put it in an array
let map = data.map((x) => x.priority);
console.log(map);
colors(map);
return (
<>
<Container>
<MaterialTable
title=""
columns={columns}
data={data}
options={options}
icons={{
Filter: (props) => <FilterListIcon style={{ fill: "#2D3155 " }} />,
Search: (props) => <SearchIcon style={{ fill: "#2D3155 " }} />,
FirstPage: (props) => <FirstPage style={{ fill: "#2D3155 " }} />,
LastPage: (props) => <LastPage style={{ fill: "#2D3155 " }} />,
NextPage: (props) => <ChevronRight style={{ fill: "#2D3155 " }} />,
PreviousPage: (props) => (
<ChevronLeft style={{ fill: "#2D3155 " }} />
),
SortArrow: (props) => (
<FilterListIcon
style={{ fill: "#2D3155 ", fontSize: "1.4em", margin: ".4em" }}
/>
),
ResetSearch: (props) => <CloseIcon style={{ fill: "#2D3155 " }} />
}}
/>
</Container>
</>
);
}
export default App;
As you can read in the docs, rowStyle accepts an object or a function.
Hence, you can set rowStyle using a function that receives rowData as parameter, an example:
const rowBackgroundColors = {
"2": "yellow", // just for example, remove it if you don't need
"3": "orange",
"4": "red",
};
const options = {
// ...
rowStyle: (rowData) => {
return {
fontFamily: "Mulish-Regular",
backgroundColor: rowBackgroundColors[rowData.priority] ?? "#fff",
};
},
// ...
};

Resources