How to use drag and drop in ant design? - javascript

What I want is an example about how to make the drag and drop of my Table that works properly, but I cannot figure out how to make it works in (functional components)
My code :
function Preview() {
const { name } = useParams();
const [fieldsOfForm, setFieldsOfForm] = useState([]);
const [selectedForm, setSelectedForm] = useState([]);
const columns = [
{
title: 'Posição',
dataIndex: 'pos',
width: 30,
className: 'drag-visible',
render: () =>
<MenuOutlined style={{ cursor: 'grab', color: '#999' }}/>
},
{
title: "Form Name",
dataIndex: "field",
key: "field",
render: (text) => <a>{text}</a>,
},
{
title: "Tipo",
dataIndex: "fieldtype",
key: "fieldtype",
},
];
useEffect(() => {
let mounted = true;
let loadingStates = loading;
if (mounted) {
setFieldsOfForm(location.state);
loadingStates.allFields = false;
setLoading(false);
}
return () => (mounted = false);
}, [selectedForm]);
return (
//Some jsx....
<Row gutter={1}>
<Col span={1}></Col>
<Table dataSource={fieldsOfForm}
columns= {columns}/>
</Row>
// More jsx...
);
}
export default Preview;
Everything that I found on internet about this drag and drop from the lib of antd is in class component , but I wanted to make it works in my functional one.
Example that I found :
multi row drag-able table (antd)
I want some example in function component if someone has tried it already and could help me ?

Here's a functional working example:
import React from "react";
import "antd/dist/antd.css";
import { Table } from "antd";
import {
sortableContainer,
sortableElement,
sortableHandle
} from "react-sortable-hoc";
import { MenuOutlined } from "#ant-design/icons";
const data = [
{
key: "1",
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
index: 0
},
{
key: "2",
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
index: 1
},
{
key: "3",
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park",
index: 2
},
{
key: "4",
name: "4",
age: 32,
address: "New York No. 1 Lake Park",
index: 3
},
{
key: "5",
name: "5",
age: 42,
address: "London No. 1 Lake Park",
index: 4
},
{
key: "6",
name: "6",
age: 32,
address: "Sidney No. 1 Lake Park",
index: 5
}
];
const DragHandle = sortableHandle(({ active }) => (
<MenuOutlined style={{ cursor: "grab", color: active ? "blue" : "#999" }} />
));
const SortableItem = sortableElement((props) => <tr {...props} />);
const SortableContainer = sortableContainer((props) => <tbody {...props} />);
function SortableTable() {
const [dataSource, setDataSource] = React.useState(data);
const [selectedItems, setSelectedItems] = React.useState([]);
const getColumns = () => {
return [
{
title: "Sort",
dataIndex: "",
width: 30,
className: "drag-visible",
render: (d, dd, i) => (
<>
<DragHandle active={selectedItems.includes(i)} />
</>
)
},
{
title: "Name",
dataIndex: "name",
className: "drag-visible"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
};
const merge = (a, b, i = 0) => {
let aa = [...a];
return [...a.slice(0, i), ...b, ...aa.slice(i, aa.length)];
};
const onSortEnd = ({ oldIndex, newIndex }) => {
let tempDataSource = dataSource;
if (oldIndex !== newIndex) {
if (!selectedItems.length) {
let movingItem = tempDataSource[oldIndex];
tempDataSource.splice(oldIndex, 1);
tempDataSource = merge(tempDataSource, [movingItem], newIndex);
} else {
let filteredItems = [];
selectedItems.forEach((d) => {
filteredItems.push(tempDataSource[d]);
});
let newData = [];
tempDataSource.forEach((d, i) => {
if (!selectedItems.includes(i)) {
newData.push(d);
}
});
tempDataSource = [...newData];
tempDataSource = merge(tempDataSource, filteredItems, newIndex);
}
setDataSource(tempDataSource);
setSelectedItems([]);
}
};
const DraggableContainer = (props) => (
<SortableContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={onSortEnd}
{...props}
/>
);
const DraggableBodyRow = ({ className, style, ...restProps }) => {
// function findIndex base on Table rowKey props and should always be a right array index
const index = dataSource.findIndex(
(x) => x.index === restProps["data-row-key"]
);
return (
<SortableItem
index={index}
{...restProps}
selected={selectedItems.length}
onClick={(e) => {
if (e.ctrlKey || e.metaKey) {
selectedItems.includes(index)
? selectedItems.splice(selectedItems.indexOf(index), 1)
: selectedItems.push(index);
setSelectedItems(selectedItems);
} else {
setSelectedItems([]);
}
}}
/>
);
};
return (
<>
<h3>"CNTRL + Click" to select multiple items</h3>
<Table
pagination={false}
dataSource={dataSource}
columns={getColumns()}
rowKey="index"
components={{
body: {
wrapper: DraggableContainer,
row: DraggableBodyRow
}
}}
/>
{selectedItems.length ? <>{selectedItems.length} items selected </> : ""}
</>
);
}
You can play with it in Sandbox

Related

react js state in parent component isn't re rendering in child component

so i am trying to make a filter for my products. And for that i initialised a state in a component called Product that has all the filters. And then, based on the input of the checkboxes that are in a child component that is called Category, the filter state changes.
here is my code :
Product.jsx:
import { useState } from "react";
import Filter from "./Filter.jsx";
import styles from "../styles/products.module.css";
const products = {
prod1: {
id: 1,
name: "prod1",
category: "laptops",
price: "80",
description: { brand: "lenovo", processor: "intel core i7" },
},
prod2: {
id: 2,
name: "prod2",
category: "laptops",
price: "10",
description: { brand: "lenovo", processor: "intel core i7" },
},
prod3: {
id: 3,
name: "prod3",
category: "laptops",
price: "100",
description: { brand: "msi", processor: "intel core i5" },
},
prod4: {
id: 4,
name: "prod4",
category: "laptops",
price: "200",
description: { brand: "msi", processor: "intel core i3" },
},
prod5: {
id: 5,
name: "prod5",
category: "phones",
price: "50",
description: { brand: "samsung", ram: "8gb", storage: "64gb" },
},
prod6: {
id: 6,
name: "prod6",
category: "phones",
price: "50",
description: { brand: "infinix", ram: "4gb", storage: "128gb" },
},
prod7: {
id: 7,
name: "prod7",
category: "phones",
price: "50",
description: { brand: "oppo", ram: "8gb", storage: "256gb" },
},
prod8: {
id: 8,
name: "prod8",
category: "accessories",
price: "99",
description: { type: "keyboard" },
},
prod9: {
id: 9,
name: "prod9",
category: "accessories",
price: "75",
description: { type: "mouse" },
},
};
function Products() {
const filter = {
laptops: {
brand: [],
processor: [],
},
phones: {
brand: [],
ram: [],
storage: [],
},
accessories: {
type: [],
},
};
const [filterList, setFilterList] = useState(filter);
const [category, setCategory] = useState("all");
const [range, setRange] = useState(9999);
const getFilterState = () => {
return filterList;
};
const setFilterState = (list) => {
setFilterList(list);
};
return (
<div className={styles.productsContainer}>
<Filter
range={[range, setRange]}
category={[category, setCategory]}
getfilterList={getFilterState}
setFilterState={setFilterState}
/>
<table className={styles.productsListContainer}>
<tr className={styles.tableColumn}>
<th className={styles.tableRow}>id</th>
<th className={styles.tableRow}>Name</th>
<th className={styles.tableRow}>price</th>
</tr>
{Object.keys(products).map((key) => {
let flag = true;
const product = products[key];
const categ = product.category;
if (
(categ === category || category === "all") &&
product.price <= range
) {
Object.entries(filter[categ]).forEach(([key, value]) => {
if (
!(
value.length === 0 || value.includes(product.description[key])
)
) {
flag = false;
}
});
if (flag) {
return (
<tr className={styles.tableColumn} key={product.id}>
<td className={styles.tableRow}>{product.id}</td>
<td className={styles.tableRow}>{product.name}</td>
<td className={styles.tableRow}>{product.price}</td>
</tr>
);
}
return null;
}
return null;
})}
</table>
</div>
);
}
export default Products;
Filter.jsx:
import CategoryComponent from "./Category";
import styles from "../styles/filter.module.css";
function Filter({
filters,
dispatch,
category,
getfilterList: filterList,
setFilterState: setFilterList,
range: rangeState,
}) {
const [Category, setCategory] = category;
const [Range, setRange] = rangeState;
const handleCategoryChanged = (event, categ, key) => {
let list = filterList();
if (list[categ][key].includes(event.currentTarget.value)) {
list[categ][key] = list[categ][key].filter(
(val) => val !== event.currentTarget.value
);
} else {
list[categ][key].push(event.currentTarget.value);
}
setFilterList(list);
if (Category !== categ) {
setCategory(categ);
} else {
let flag = false;
Object.keys(filterList()[categ]).forEach((key) => {
const obj = filterList()[categ][key];
if (obj.length !== 0) flag = true;
});
if (!flag) {
setCategory("all");
}
}
};
const handleRangeChanged = (event) => {
setRange(event.currentTarget.value);
};
return (
<div className="filter-container">
<h1>{Category}</h1>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
category={Category}
handleCategoryChanged={handleCategoryChanged}
categories={{
brand: ["lenovo", "msi", "asus"],
processor: ["intel core i3", "intel core i5", "intel core i7"],
}}
>
laptops
</CategoryComponent>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
handleCategoryChanged={handleCategoryChanged}
category={Category}
categories={{
brand: ["samsung", "infinx", "oppo"],
ram: ["8gb", "4gb", "2gb"],
storage: ["64gb", "128gb", "256gb"],
}}
>
phones
</CategoryComponent>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
category={Category}
handleCategoryChanged={handleCategoryChanged}
categories={{
type: ["keyboard", "mouse", "screen"],
}}
>
accessories
</CategoryComponent>
<div className={styles.priceRangeContainer}>
<input
type="range"
min="0"
max="9999"
className={styles.priceRange}
value={Range}
onChange={handleRangeChanged}
/>
<span>{Range}DT</span>
</div>
</div>
);
}
export default Filter;
Category.jsx
import React from "react";
import { useState, useEffect } from "react";
import styles from "../styles/category.module.css";
import arrow from "../img/angle-up-solid-svgrepo-com.svg";
export default function Category({
handleCategoryChanged,
children,
categories,
category,
filterList,
setFilterList,
}) {
const [hiddenClass, setHiddenClass] = useState(styles.hidden);
const handleClick = () => {
if (hiddenClass) setHiddenClass("");
else setHiddenClass(styles.hidden);
};
return (
<div>
<h5>{category}</h5>
<div onClick={handleClick} className={styles.categoryBox}>
{children} <img style={{ width: 15 }} src={arrow} alt="" />
</div>
<div className={styles.hiddenScroll + " " + hiddenClass}>
{Object.entries(categories).map(([key, value]) => {
return (
<div key={key}>
<h5>{key}:</h5>
<ul>
{value.map((val) => {
console.log(filterList);
return (
<li key={val}>
<input
type="checkbox"
name={val}
id={val}
value={val}
onChange={(e) =>
handleCategoryChanged(e, children, key)
}
/>
<label htmlFor={val}>{val}</label>
</li>
);
})}
</ul>
</div>
);
})}
</div>
</div>
);
}
The problem is when i run my program the state changes when i check an only one checkbox, but when i click on more checkboxes nothing changes.
EDIT:
my components hierarchy :
products.jsx
filter.jsx
Category.jsx
in products.jsx :
i have a state filterList that has all the filters i need.
const filter = {
laptops: {
brand: [],
processor: [],
},
phones: {
brand: [],
ram: [],
storage: [],
},
accessories: {
type: [],
},
};
const [filterList, setFilterList] = useState(filter);
in Filter.jsx:
i have state filterList that was passed as a prop from the Products component.
and a function that handles any checkbox change from it's child component.
const handleCategoryChanged = (event, categ, key) => {
let list = filterList();
//testing if the value is already in the filterList if true we remove it
if (list[categ][key].includes(event.currentTarget.value)) {
list[categ][key] = list[categ][key].filter(
(val) => val !== event.currentTarget.value
);
} else {
// else we add it to the list
list[categ][key].push(event.currentTarget.value);
}
setFilterList(list);
Category.jsx:
here i created all my checkboxes with the onChange event.
But the problem is when more than one checkbox gets changed, my filterList state does not get re rendered.
You missed the useState in your Filter.jsx
Replacing
const [Category, setCategory] = category;
const [Range, setRange] = rangeState;
With
const [Category, setCategory] = useState(category);
const [Range, setRange] = useState(rangeState);
A small working example could be found at code sandbox
⚠️ You did not provide the parent of Filter.jsx so in the example only the laptop brands are working

How to fix the array state filtering data in react hooks

import { useState } from "react";
function App() {
const [itemsClicked, setItemsClicked] = useState([]);
const dataList = [
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
];
const highlight = (data) => {
setItemsClicked((array) => {
let itemClicked = array.includes(data)
? array.filter((x, i) => x.id !== data.id)
: [...array, data]; // What I'm trying to do here is to add a new field which is 'active' >> [...array, {...data, active: true}];
return itemClicked;
});
};
return (
<div>
{dataList.map((item, i) => (
<div
style={{
borderColor: itemsClicked[i]?.active ? "1px solid green" : ""
}}
>
<button onClick={() => highlight(dataList[i])}>HIGHLIGHT</button>
</div>
))}
</div>
);
}
export default App;
What I'm trying to do here is to create a toggle which is to highlight the div.
but my problem is on the state instead of it will remove the data object, it will continue appending. how to fix it?
for example when I try to click the first data which is jake, the output should be like this in itemsClicke
[{ id: 1, name: jake, active: true }]
but when I try to click again it will just remove it from the list, but on my side, it continuing to append the data which is wrong
[{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true },{ id: 1, name: jake, active: true }]
You can simply remove the itemsClicked state and put the dataList in a state variable to have control over it:
const [dataList, setDataList] = useState([
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
]);
Now, you need to check an isActive property to conditionally add some styles, so you need isActive but it's not in the current dataList and you created itemsClicked to solve this issue. But there are some simpler solutions like adding a property on the fly with your dataList.
You can implement toggleHighlight function to change isActive property:
const toggleHighlight = (id) => {
setDataList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, isActive: !item.isActive } : item
)
);
};
this toggleHandler will accept an id and take a for loop over the dataList, it finds the clicked item. if it's an active item so it changes it to false and vice versa.
let's recap and put all things together:
import { useState } from "react";
function App() {
const [dataList, setDataList] = useState([
{ id: 1, name: "jake" },
{ id: 12, name: "edd" },
{ id: 13, name: "john" }
]);
const toggleHighlight = (id) => {
setDataList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, isActive: !item.isActive } : item
)
);
};
return (
<div>
{dataList.map((item) => (
<div
key={item.id}
style={{
display: "flex",
paddingTop: 10,
border: item.isActive ? "2px solid green" : ""
}}
>
<p style={{ padding: 5 }}>{item.name}</p>
<button onClick={() => toggleHighlight(item.id)}>HIGHLIGHT</button>
</div>
))}
</div>
);
}
export default App;
Try this on a live playground
array.includes(data)
won't return true because of this: https://stackoverflow.com/a/50371323/17357155
Just use this. It's a simple example. I used itemsClicked only for storing the ids
const highlight = (data) => {
if(itemsClicked.indexOf(data.id) == -1)
{
setItemsClicked(prevData => {
return [...prevData, data.id]
})
}
}
{
dataList.map((item, i) => (
<div
style={{
border:
itemsClicked.indexOf(item.id) > -1 ? '1px solid green' : '',
}}
>
{itemsClicked.indexOf(item.id) > -1 ? '1px solid green' : ''}
<button onClick={() => highlight(dataList[i])}>HIGHLIGHT</button>
</div>
))
}

react Material-table unable to change each icon set color? [duplicate]

This question already has answers here:
How can I change the color of material-table icons of onRowAdd, onRowUpdate, onRowDelete?
(2 answers)
Closed 1 year ago.
I am using the material-table library and trying to change each icon color. Would you please help me to do so? I tried custom CSS but it's changing all icon color at once not the specific one?
here is my code sandbox link.
In short: I want to change color of each of three icons add, edit, delete
Here is my code I am using a material-table library with material-ui. Also is it possible to change these default icons?
List.js
import React, { useState } from 'react';
import './App.css';
import MaterialTable from 'material-table'
import makeStyles from "#material-ui/core/styles/makeStyles";
const useStyles = makeStyles(theme => ({
rootTable: {
'& .MuiIconButton-colorInherit' : {
color: '#6a2cd8'
},
'& .MuiIconButton-root .MuiIconButton-colorInherit': {
color: 'red'
},
width: theme.spacing(150)
}
}));
const empList = [
{ id: 1, name: "Neeraj", email: 'neeraj#gmail.com', phone: 9876543210, city: "Bangalore" },
{ id: 2, name: "Raj", email: 'raj#gmail.com', phone: 9812345678, city: "Chennai" },
{ id: 3, name: "David", email: 'david342#gmail.com', phone: 7896536289, city: "Jaipur" },
{ id: 4, name: "Vikas", email: 'vikas75#gmail.com', phone: 9087654321, city: "Hyderabad" },
]
function App() {
const [data, setData] = useState(empList)
const columns = [
{ title: "ID", field: "id", editable: false },
{ title: "Name", field: "name" },
{ title: "Email", field: "email" },
{ title: "Phone Number", field: 'phone', },
{ title: "City", field: "city", }
]
const classes = useStyles();
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Material Table with CRUD operation</h4>
<div className={classes.rootTable}>
<MaterialTable
title="Employee Data"
data={data}
columns={columns}
editable={{
onRowAdd: (newRow) => new Promise((resolve, reject) => {
const updatedRows = [...data, { id: Math.floor(Math.random() * 100), ...newRow }]
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowDelete: selectedRow => new Promise((resolve, reject) => {
const index = selectedRow.tableData.id;
const updatedRows = [...data]
updatedRows.splice(index, 1)
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
}),
onRowUpdate:(updatedRow,oldRow)=>new Promise((resolve,reject)=>{
const index=oldRow.tableData.id;
const updatedRows=[...data]
updatedRows[index]=updatedRow
setTimeout(() => {
setData(updatedRows)
resolve()
}, 2000)
})
}}
options={{
actionsColumnIndex: -1, addRowPosition: "first"
}}
/>
</div>
</div>
);
}
export default App;
You can import icons you wish to change their color from #material-ui/icons, change the color you want, and pass them as icons prop to MaterialTable: codesandbox.
import { Edit } from "#material-ui/icons";
...
icons={{
Edit: React.forwardRef((props, ref) => (
<Edit style={{ color: "green" }} {...props} ref={ref} />
))
}}

Show dynamically added row in expanded form in ant tables

I have an ant table where I pass a parameter "defaultExpandAllRows=true" which renders the table in an expanded form.
<Table
columns={columns}
pagination={false}
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
defaultExpandAllRows: true,
rowExpandable: (record) => record.name !== "Not Expandable"
}}
dataSource={customScopeTableData}
/>
My use case is to show the dynamically added row in expanded form.
Here is the working sample code
https://codesandbox.io/s/dynamic-expandable-row-issue-f6bn5?file=/index.js
I couldn't find something in the API doc.
Any help on this would be appreciable.
I think you should use expandedRowKeys instead of defaultExpandAllRows. Because of defaultExpandAllRows only apply to the initial data, so newly added rows won't be affected.
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
rowExpandable: (record) => record.name !== "Not Expandable",
expandedRowKeys: expandedRowKeys,
onExpand: (expanded, record) => {
updateExpandedRowKeys({ record });
}
}}
First create a state for expandedRowKeys and a function for update expandedRowKeys
const [expandedRowKeys, setExpandedRowKeys] = useState(data.map(({ key }) => key));
const updateExpandedRowKeys = ({ record }) => {
const rowKey = record.key;
const isExpanded = expandedRowKeys.find(key => key === rowKey);
let newExpandedRowKeys = [];
if (isExpanded) {
newExpandedRowKeys = expandedRowKeys.reduce((acc, key) => {
if (key !== rowKey) acc.push(key);
return acc;
}, []);
} else {
newExpandedRowKeys = expandedRowKeys;
newExpandedRowKeys.push(rowKey);
}
setExpandedRowKeys(newExpandedRowKeys);
};
Then insert new expandedRowKey when new row added
const addCustomField = () => {
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
setCustomScopeTableData([...customScopeTableData, newEntry(newIndex)]);
setExpandedRowKeys([...expandedRowKeys, newIndex])
};
Column key for newEntry should be unique, Date.now() seems will be duplicated. I have changed it to incremental index.
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Table, Button } from "antd";
const columns = [
{ title: "Name", dataIndex: "name", key: "name" },
{ title: "Age", dataIndex: "age", key: "age" },
{ title: "Address", dataIndex: "address", key: "address" },
{
title: "Action",
dataIndex: "",
key: "x",
render: () => <a>Delete</a>
}
];
const data = [
{
key: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
description:
"My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park."
},
{
key: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
description:
"My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park."
},
{
key: 3,
name: "Not Expandable",
age: 29,
address: "Jiangsu No. 1 Lake Park",
description: "This not expandable"
},
{
key: 4,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park",
description:
"My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park."
}
];
const newEntry = (key) => {
return {
key: key,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park",
description:
"My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park."
}
};
const TableComponent = () => {
const [customScopeTableData, setCustomScopeTableData] = useState(data);
const addCustomField = () => {
const newIndex = customScopeTableData.slice(-1)[0].key + 1;
setCustomScopeTableData([...customScopeTableData, newEntry(newIndex)]);
setExpandedRowKeys([...expandedRowKeys, newIndex])
};
const [expandedRowKeys, setExpandedRowKeys] = useState(data.map(({ key }) => key));
const updateExpandedRowKeys = ({ record }) => {
const rowKey = record.key;
const isExpanded = expandedRowKeys.find(key => key === rowKey);
let newExpandedRowKeys = [];
if (isExpanded) {
newExpandedRowKeys = expandedRowKeys.reduce((acc, key) => {
if (key !== rowKey) acc.push(key);
return acc;
}, []);
} else {
newExpandedRowKeys = expandedRowKeys;
newExpandedRowKeys.push(rowKey);
}
setExpandedRowKeys(newExpandedRowKeys);
};
return (
<>
<Table
columns={columns}
pagination={false}
expandable={{
expandedRowRender: (record) => (
<p style={{ margin: 0 }}>{record.description}</p>
),
rowExpandable: (record) => record.name !== "Not Expandable",
expandedRowKeys: expandedRowKeys,
onExpand: (expanded, record) => {
updateExpandedRowKeys({ record });
}
}}
dataSource={customScopeTableData}
/>
<Button type="dashed" onClick={() => addCustomField()}>
Add field
</Button>
</>
);
};
ReactDOM.render(<TableComponent />, document.getElementById("container"));

How to update particular object in array of multiple objects Dynamically

How to update a particular object in array of objects. For example,
have an object like this
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-039e3a8f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a39-7e6e-4eb2-b974-03e3a82f03f0"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
and my new value should update is
{
DraggedItemRandomId: "70d19a9f-739e-4eb2-b974-03e3a8f032d1",
draggedItem:{
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200}
}
}
How can i Update this object in state (0th or 1st object dynamically)
object like
how can i do setState for inner loop of array of objects dynamically???
i have tried so many examples but no result ..please help me guyz
final output:
tabs: [{
name: "Step 1",
DroppedDetails: [
{id:1,name:Step1,
DroppedDetails:[
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Hey Sagar" // updated value
readOnly: true //updated value
required: true //updated value
width: 200
}
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}
]
},
{
name: "Step 2",
DroppedDetails: [
{DraggedItemRandomId: "70d19a9f-7e6e-4eb2-b974-03e3a8f03f08"
draggedItem:
category: "basic"
disabled: false
fieldClass: "Text"
height: 30
id: "text"
image: "/static/media/type.327c33c2.png"
label: "Text Field"
placeholder: "Edit This Field"
readOnly: false
required: false
width: 200
}]
}
],
You can get, first of all, the tabs of the state:
const { tabs } = this.state;
// here you code to decide what tab do you want to update
const tabSelected = tabs[0];
const { DroppedDetails } = tabSelected;
DroppedDetails[0]= {
name: "sagar111"
};
// You can edit another one or add a new one to the array also.
DroppedDetails[1]= {
name: "NameEdited"
};
DroppedDetails.push({ name: "New Name" })
And set state a new state:
this.setState(
{ tabs: tabs.map(t => t === tabSelected ? { ...tabSelected, DroppedDetails }) : t });
But it could be in this way too:
this.setState(tabs);
Because the original references were updated.
At the end DroppedDetails and tabs[0].DroppedDetails[0] are the same object.
how can i do setState for inner loop of array of objects???
It's not recommended the use of setState in a forloop.
Check this Calling setState in a loop only updates state 1 time
You can easily do that (this changes the name to lower case):
const { tabs } = this.state;
tabs.map(tab => {
// Do something with your tab value like the following
const newDroppedDetails = tab.map(({ name }) => ({ name: name.toLowerCase() });
return {
...tab,
DroppedDetails: newDroppedDetails
}
});
The key point here is to not mutate objects or arrays, but to create a new reference to objects that you modify, so the shallow compare on PureComponents will always work properly.
Applying the same concepts from your original codesandbox, we can do something like this to edit each individual Dropdown Detail.
working sandbox https://codesandbox.io/s/tab-creator-v2-8hc7c
import React from "react";
import ReactDOM from "react-dom";
import uuid from "uuid";
import "./styles.css";
class App extends React.Component {
state = {
tabs: [
{
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
{
id: 2,
name: "Tab 2",
content: "Look at me, it's Tab 2",
DroppedDetails: [
{ name: "Christopher", editing: false },
{ name: "Ngo", editing: false }
]
}
],
currentTab: {
id: 1,
name: "Tab 1",
content: "Wow this is tab 1",
DroppedDetails: [
{ name: "Bhanu", editing: false },
{ name: "Sagar", editing: false }
]
},
editMode: false,
editTabNameMode: false
};
handleDoubleClick = () => {
this.setState({
editTabNameMode: true
});
};
handleEditTabName = e => {
const { currentTab, tabs } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.id === currentTab.id) {
return {
...tab,
name: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
name: e.target.value
}
});
};
handleOnBlur = () => {
this.setState({
editTabNameMode: false
});
};
handleDetailChange = (e, id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
name: e.target.value
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: updatedCurrentTab
});
};
createTabs = () => {
const { tabs, currentTab, editTabNameMode } = this.state;
const allTabs = tabs.map(tab => {
return (
<li>
{editTabNameMode && currentTab.id === tab.id ? (
<input
value={tab.name}
onBlur={this.handleOnBlur}
onChange={this.handleEditTabName}
/>
) : (
<button
className={currentTab.id === tab.id ? "tab active" : "tab"}
onClick={() => this.handleSelectTab(tab)}
onDoubleClick={() => this.handleDoubleClick(tab)}
>
{tab.name}
</button>
)}
</li>
);
});
return <ul className="nav nav-tabs">{allTabs}</ul>;
};
handleSelectTab = tab => {
this.setState({
currentTab: tab,
editMode: false,
editTabNameMode: false
});
};
handleAddTab = () => {
const { tabs } = this.state;
const newTabObject = {
id: uuid(),
name: `Tab ${tabs.length + 1}`,
content: `This is Tab ${tabs.length + 1}`,
DroppedDetails: []
};
this.setState({
tabs: [...tabs, newTabObject],
currentTab: newTabObject,
editMode: false,
editTabNameMode: false
});
};
handleDeleteTab = tabToDelete => {
const { tabs } = this.state;
const tabToDeleteIndex = tabs.findIndex(tab => tab.id === tabToDelete.id);
const updatedTabs = tabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab =
tabs[tabToDeleteIndex - 1] || tabs[tabToDeleteIndex + 1] || {};
this.setState({
tabs: updatedTabs,
editMode: false,
editTabNameMode: false,
currentTab: previousTab
});
};
setEditMode = () => {
this.setState({
editMode: !this.state.editMode
});
};
handleContentChange = e => {
const { tabs, currentTab } = this.state;
const updatedTabs = tabs.map(tab => {
if (tab.name === currentTab.name) {
return {
...tab,
content: e.target.value
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs,
currentTab: {
...currentTab,
content: e.target.value
}
});
};
handleOnDetailBlur = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: false
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: false
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
handleDoubleClickDetail = (id, index) => {
const { tabs, currentTab } = this.state;
const updatedCurrentTab = { ...currentTab };
updatedCurrentTab.DroppedDetails = updatedCurrentTab.DroppedDetails.map(
(detail, detailIndex) => {
if (index == detailIndex) {
return {
...detail,
editing: true
};
} else {
return detail;
}
}
);
const updatedTabs = tabs.map(tab => {
if (tab.id == id) {
return {
...tab,
DroppedDetails: tab.DroppedDetails.map((detail, detailIndex) => {
if (detailIndex == index) {
return {
...detail,
editing: true
};
} else {
return detail;
}
})
};
} else {
return tab;
}
});
this.setState({
tabs: updatedTabs || [],
currentTab: updatedCurrentTab
});
};
createContent = () => {
const { currentTab } = this.state;
return (
<div>
<div>
<p>{currentTab.content}</p>
<div>
<h4>Dropped Details</h4>
{currentTab.DroppedDetails ? (
<div>
{currentTab.DroppedDetails.map((detail, index) => {
if (detail.editing) {
return (
<div>
<input
value={detail.name}
onChange={e =>
this.handleDetailChange(e, currentTab.id, index)
}
onBlur={() =>
this.handleOnDetailBlur(currentTab.id, index)
}
/>
</div>
);
} else {
return (
<p
onDoubleClick={() =>
this.handleDoubleClickDetail(currentTab.id, index)
}
>
{detail.name}
</p>
);
}
})}
</div>
) : (
""
)}
</div>
</div>
{currentTab.id ? (
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button className="edit-mode-button" onClick={this.setEditMode}>
Edit
</button>
<button onClick={() => this.handleDeleteTab(currentTab)}>
Delete
</button>
</div>
) : (
""
)}
</div>
);
};
render() {
const { currentTab, editMode } = this.state;
return (
<div className="container">
<div className="well">
<button className="add-tab-button" onClick={this.handleAddTab}>
<i className="text-primary fas fa-plus-square" /> Add Tab
</button>
{this.createTabs()}
<div className="tab-content">
{editMode ? (
<div>
<textarea
onChange={this.handleContentChange}
value={currentTab.content}
/>
<button className="save-button" onClick={this.setEditMode}>
Done
</button>
</div>
) : (
this.createContent()
)}
</div>
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
To activate "edit-mode" double-click a dropped-detail. The input should appear in its place and you can type in the new text. When complete, click out of the input and it will finalize the updated text :)

Categories

Resources