I am using an antd tree component my issue is if you search something in the search bar then you will get search results in that result if you check and up check any field what happens all previous data get unchecked whatever data is present into the search bar result that only data remain selected if it already select or you just select what my task is I don't want to get unchecked all previously selected checked that only field update that we change right now I don't have any idea how can I fix this if anybody knows anyway, also I added a complete code SandBox link below.
This is my search bar filter code
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm) =>
arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm)?.length > 0
);
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, checkedKeys)
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
This issue happens when the check or unchecks field after searching in the search bar in this part of the code
const onCheck = (checkedKeysValue) => {
console.log("onCheck", checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const onExpand = (expandedKeysValue) => {
console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = (checkedKeysValue) => {
console.log("onCheck", checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
React.useEffect(() => {
const checked = [];
treeData.forEach((data) => {
data.children.forEach((item) => {
if (item.checked) {
checked.push(item.key);
}
});
});
setCheckedKeys(checked);
}, []);
React.useEffect(() => {
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
</div>
);
};
CodeSandBox Link
Edited Response to fix uncheck issue
If I understood the question correctly, Is the issue with the fact that previous checked items are getting cleared on search and selection of new one?
I think the solution would be to use 2 different separate trees one for filtered and the other for normal.
Did some code changes on top of the sandbox code shared.
I have added a new tree when searchedValue is present.
On checking/unchecking the new filtered tree, the actual entire tree's checked values get updated
When the searched value is empty it would show the actual entire tree
Created a filteredKeys list to solve uncheck issue. Now I am able to select and unselect.
If you play around and refactor a bit, you should be able to achieve what you want.
Adding the same code below.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
const { Search } = Input;
const treeData = [
{
title: "AP Watchlists",
key: "AP Watchlists",
children: [
{ title: "Colo Open Data", key: "Colo Open Data", checked: true },
{
title: "Department and trade",
key: "Department and trade",
checked: true
},
{
title: "North List",
key: "North List",
checked: true
},
{ title: "People's Daily", key: "People's Daily", checked: true }
]
},
{
title: "Af Watchlists",
key: "Af Watchlists",
children: [
{
title: "Service Wanted List",
key: "Service Wanted List",
checked: true
}
]
},
{
title: "EM Watchlists",
key: "EM Watchlists",
children: [
{
title: "National Financing",
key: "National Financing",
checked: true
},
{
title: "Arabia List",
key: "Arabia List",
checked: true
}
]
},
{
title: "Assets List",
key: "Assets List",
children: [
{
title: "National List",
key: "National List",
checked: true
}
]
},
{
title: "New Watchlists",
key: "New Watchlists",
children: [
{ title: "FATR", key: "FATR", checked: true },
{ title: "Internal", key: "Internal", checked: true },
{
title: "OC List (Covers 73 Lists)",
key: "OC List (Covers 73 Lists)",
checked: true
},
{ title: "UN", key: "UN", checked: true },
{
title: "Security List (Covers 18 Lists)",
key: "Security List (Covers 18 Lists)",
checked: true
}
]
}
];
const Demo = () => {
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm, keys) => {
const result = arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm, keys)?.length > 0
);
result &&
result.forEach((node) => {
if (keys.indexOf(node?.key) === -1) keys.push(node?.key);
});
return result;
};
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const keys = [];
const filteredData = searchString
? filterData(data, searchString, keys).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, keys)
};
})
: data;
setExpandedTree([...keysToExpand]);
setFilteredKeys(keys);
return filteredData;
}
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [filteredCheckedKeys, setFilteredCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const [filteredTree, setFilteredTree] = useState([]);
const [filteredKeys, setFilteredKeys] = useState([]);
const onExpand = (expandedKeysValue) => {
// console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = (checkedKeysValue) => {
// console.log("onCheck", checkedKeysValue);
// console.log("checkedKeys", checkedKeys);
setCheckedKeys(checkedKeysValue);
};
const onFilteredTreeCheck = (checkedKeysValue) => {
// console.log("onFilterCheck", checkedKeysValue);
// console.log("filteredcheckedKeys", filteredCheckedKeys);
setFilteredCheckedKeys(checkedKeysValue);
const baseTreeKeys = [...checkedKeys].filter(
(node) => filteredKeys.indexOf(node) === -1
);
console.log("baseTreeKeys", baseTreeKeys);
console.log("checkedKeysValue", checkedKeysValue);
setCheckedKeys([...checkedKeysValue, ...baseTreeKeys]);
};
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
// React.useEffect(() => {
// const checked = [];
// treeData.forEach((data) => {
// data.children.forEach((item) => {
// if (item.checked) {
// checked.push(item.key);
// }
// });
// });
// setCheckedKeys(checked);
// }, []);
React.useEffect(() => {
setFilteredKeys([]);
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setFilteredTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
console.log("filteredCHeckedValues", filteredCheckedKeys);
console.log("existing checked values", checkedKeys);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
{searchValue ? (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onFilteredTreeCheck}
checkedKeys={filteredCheckedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={filteredTree}
/>
) : (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
)}
</div>
);
};
ReactDOM.render(<Demo />, document.getElementById("container"));
Let me know if this solves your issue. I am able to select multiple values in subsequent searches without loosing the checked ones.
As mentioned in the API document, filterTreeNode will keep keys from the tree node, and will not hide it.
filterTreeNode
Defines a function to filter treeNodes. When the function returns true, the corresponding treeNode will be checked
If you want to hide tree node, you will have to manually filter it first before before passing it to Tree in loop function, something like:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
const gData = [
{
key: "1",
title: "title 1"
},
{
key: "2",
title: "title 2"
},
{
key: "3",
title: "title 3",
children: [
{
key: "4",
title: "title 4"
},
{
key: "5",
title: "title 5",
children: [
{
key: "6",
title: "title 6"
},
{
key: "7",
title: "title 7"
}
]
}
]
}
];
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [treeData, setTreeData] = useState(gData);
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const value = e.target.value?.toLowerCase();
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
const hasSearchTerm = (n) => n.toLowerCase().indexOf(value) !== -1;
const filterData = (arr) =>
arr?.filter(
(n) => hasSearchTerm(n.title) || filterData(n.children)?.length > 0
);
const filteredData = filterData(gData).map((n) => {
return {
...n,
children: filterData(n.children)
};
});
setTreeData(filteredData);
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setTreeData(gData);
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(treeData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
Related
I'm trying to render some tags from a source and then put it into a buttons in random form but the issue comes after click the button my component re-render for no reason.
why can i do?...
const Blog = () => {
const [articles, setArticles] = useState([]);
const [tags, setTags] = useState([]);
// consult to database
const getData = async () => {
try {
// get aticles, tags
setArticles([
{ name: "title1", id_tag: 1 },
{ name: "title2", id_tag: 2 }
]);
setTags([
{ id: 1, name: "bell" },
{ id: 2, name: "fashion" }
]);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getData();
}, []);
const getRandomTag = (i) => {
const newTags = tags;
const tag = newTags[Math.floor(Math.random() * newTags.length)];
return (
<button key={i} onClick={() => filterByTag(tag.id)}>
{tag.name}
</button>
);
};
// filter the article by tag
const filterByTag = (id) => {
setArticles(articles.filter((article) => article.id_tag === id));
};
return (
<>
<ul>
{articles.map((article, i) => (
<li key={i}>{article.name}</li>
))}
</ul>
<h4>TAGS</h4>
<ul>{tags.map((tag, i) => getRandomTag(i))}</ul>
</>
);
export default Blog;
https://codesandbox.io/s/heuristic-solomon-sp640j?from-embed=&file=/src/Blog.js
That's because your component re-renders whenever any state inside it or any props it receives changes either by value or reference. And whenever you map something without caching its value it maps on each rerender. You have tu either split it into other component or use caching.
import { useCallback, useEffect, useMemo, useState } from "react";
const Blog = () => {
const [articles, setArticles] = useState([]);
const [tags, setTags] = useState([]);
// consult to database
const getData = async () => {
try {
// get aticles, tags
setArticles([
{ name: "title1", id_tag: 1 },
{ name: "title2", id_tag: 2 },
{ name: "title3", id_tag: 3 },
{ name: "title4", id_tag: 4 },
{ name: "title5", id_tag: 5 },
{ name: "title6", id_tag: 6 }
]);
setTags([
{ id: 1, name: "bell" },
{ id: 2, name: "fashion" },
{ id: 3, name: "fancy" },
{ id: 4, name: "tag" },
{ id: 6, name: "name" },
]);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
getData();
}, []);
const filterByTag = useCallback((id) => {
setArticles(currentArticles => currentArticles.filter((article) => article.id_tag === id));
}, [setArticles]);
const getRandomTag = useCallback(
(i) => {
const newTags = tags;
const tag = newTags[Math.floor(Math.random() * newTags.length)];
return (
<button key={i} onClick={() => filterByTag(tag.id)}>
{tag.name}
</button>
);
},
[tags, filterByTag]
);
// filter the article by tag
const renderTags = useMemo(() => {
return tags.map((tag, i) => getRandomTag(i));
}, [tags, getRandomTag]);
return (
<>
<ul>
{articles.map((article, i) => (
<li key={i}>{article.name}</li>
))}
</ul>
<h4>TAGS</h4>
<ul>{renderTags}</ul>
</>
);
};
export default Blog;
Here you have a working example of caching https://codesandbox.io/s/sad-shadow-8pogoc?file=/src/Blog.js
I have a component that uses React.useState to keep a property named available
and what I want to do is to change its value to true with a conditional statement, so that my component gets rendered based on that condition, but I can't set up a conditional statement inside React.useState. I tried changing it in my other component with a conditional statement:
const [isUserLogged] = React.useState(true);
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
if (isUserLogged === true) {
props.available === true;
}
But that didn't work too. How can I achieve this with a conditional statement? Here is my entire code:
import * as React from 'react';
import { withRouter } from 'react-router-dom';
import {
Drawer,
DrawerContent,
DrawerItem,
} from '#progress/kendo-react-layout';
import { Button } from '#progress/kendo-react-buttons';
const CustomItem = (props) => {
const { visible, ...others } = props;
const [isUserLogged] = React.useState(true);
const arrowDir = props['data-expanded']
? 'k-i-arrow-chevron-down'
: 'k-i-arrow-chevron-right';
if (isUserLogged === true) {
props.available === true;
}
return (
<React.Fragment>
{props.available === false ? null : (
<DrawerItem {...others}>
<span className={'k-icon ' + props.icon} />
<span className={'k-item-text'}>{props.text}</span>
{props['data-expanded'] !== undefined && (
<span
className={'k-icon ' + arrowDir}
style={{
position: 'absolute',
right: 10,
}}
/>
)}
</DrawerItem>
)}
</React.Fragment>
);
};
const DrawerContainer = (props) => {
const [drawerExpanded, setDrawerExpanded] = React.useState(true);
const [isUserLogged] = React.useState(true);
const [items, setItems] = React.useState([
{
text: 'Education',
icon: 'k-i-pencil',
id: 1,
selected: true,
route: '/',
},
{
separator: true,
},
{
text: 'Food',
icon: 'k-i-heart',
id: 2,
['data-expanded']: true,
route: '/food',
},
{
text: 'Japanese Food',
icon: 'k-i-minus',
id: 4,
parentId: 2,
route: '/food/japanese',
},
{
text: 'Secret Food',
icon: 'k-i-minus',
id: 5,
parentId: 2,
route: '/food/italian',
available: false,
},
{
separator: true,
},
{
text: 'Travel',
icon: 'k-i-globe-outline',
['data-expanded']: true,
id: 3,
route: '/travel',
},
{
text: 'Europe',
icon: 'k-i-minus',
id: 6,
parentId: 3,
route: '/travel/europe',
},
{
text: 'North America',
icon: 'k-i-minus',
id: 7,
parentId: 3,
route: '/travel/america',
},
]);
const handleClick = () => {
setDrawerExpanded(!drawerExpanded);
};
const onSelect = (ev) => {
const currentItem = ev.itemTarget.props;
const isParent = currentItem['data-expanded'] !== undefined;
const nextExpanded = !currentItem['data-expanded'];
const newData = items.map((item) => {
const {
selected,
['data-expanded']: currentExpanded,
id,
...others
} = item;
const isCurrentItem = currentItem.id === id;
return {
selected: isCurrentItem,
['data-expanded']:
isCurrentItem && isParent ? nextExpanded : currentExpanded,
id,
...others,
};
});
props.history.push(ev.itemTarget.props.route);
setItems(newData);
};
const data = items.map((item) => {
const { parentId, ...others } = item;
if (parentId !== undefined) {
const parent = items.find((parent) => parent.id === parentId);
return { ...others, visible: parent['data-expanded'] };
}
return item;
});
return (
<div>
<div className="custom-toolbar">
<Button icon="menu" look="flat" onClick={handleClick} />
<span className="title">Categories</span>
</div>
<Drawer
expanded={drawerExpanded}
mode="push"
width={180}
items={data}
item={CustomItem}
onSelect={onSelect}
>
<DrawerContent>{props.children}</DrawerContent>
</Drawer>
</div>
);
};
export default withRouter(DrawerContainer);
if props.available is present when your component is rendering you can write a conditional expression while declaring the isLoggedIn inside useState.
In case it is available later we can always use useEffect hook to update the isLoggedIn
const Component = (props) => {
// if props.available is already present to you
const [isLoggedIn, setIsLoggedIn] = React.useState(props.isAvailable ? true : false);
// if props.isAvailable is not present when your component renders you can use
// useEffect
React.useEffect(() => {
setIsLoggedIn(props.isAvailable);
}, [props.isAvailable])
// use IsLoggedIN here
return (
<div>
{
isLoggedIn ?
<div> Logged in </div>
: <div>llogged out</div>
}
</div>
)
}
I have created two tabs that when clicked need to show a different set of products and a different set of filters for each selection. My problem is that when I click either tab and call setOptions within changeTab, I need to click each tab twice before it will update 'options', 'options' needs to contain each filter.
Obviously calling setOptions within the click handler is not correct but I can't figure out where or how to correctly update 'options'. Help greatly appreciated.
In the console logs below 'dedupedOptions' updates correctly on click
function filterProducts() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const {
productData: {
products: { products }
}
} = useContext(AppContext);
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
let dedupedOptions = [];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!dedupedOptions.find((dedupedOption) => dedupedOption.value === value)
) {
dedupedOptions = [
...dedupedOptions,
{
label: titleCase(value),
value,
selected: false
}
];
}
});
});
const [options, setOptions] = useState(dedupedOptions);
console.log(dedupedOptions);
console.log(options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
setOptions(dedupedOptions);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
}
And the two elements set up as tabs to handle the click events. These are rendered within the same filterProducts function.
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
I reproduced your question by some changes in variable declarations in state.
be careful to declare variables in state and do the updates by listening the variable changes inside the useEffect.
here is the working code:\
https://codesandbox.io/s/quirky-http-e264i?file=/src/App.js
import "./styles.css";
import { useState, useContext, useCallback, useEffect } from "react";
export default function App() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const [productsByCategory, setProductsByCategory] = useState([]);
const [dedupedOptions, setDedupedOptions] = useState([]);
const [options, setOptions] = useState(dedupedOptions);
const products = [
{ tags: ["category:Feline"], name: "one" },
{ tags: ["category:Canine"], name: "two" }
];
useEffect(() => {
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
setProductsByCategory(productsByCategory);
}, [categoryType]);
useEffect(() => {
let tmp_dedupedOptions = [];
const tagKeysToDisplay = ["category"];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!tmp_dedupedOptions.find(
(dedupedOption) => dedupedOption.value === value
)
) {
tmp_dedupedOptions = [
...tmp_dedupedOptions,
{
label: value,
value,
selected: false
}
];
}
});
});
setDedupedOptions(tmp_dedupedOptions);
setOptions(tmp_dedupedOptions);
}, [productsByCategory]);
console.log("options: ", options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
// }
return (
<div>
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
</div>
);
}
I am replacing a class component of react-tag with a function component.
The original one is here https://stackblitz.com/edit/react-tag-input-1nelrc?file=index.js
However , Tag it does not work well...
How should I fix it?
Error says
×
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: [{ id: 'Thailand', text: 'Thailand' }, { id: 'India', text: 'India' }],
suggestions: suggestions,
};
this.handleDelete = this.handleDelete.bind(this);
this.handleAddition = this.handleAddition.bind(this);
this.handleDrag = this.handleDrag.bind(this);
this.handleTagClick = this.handleTagClick.bind(this);
}
handleDelete(i) {
const { tags } = this.state;
this.setState({
tags: tags.filter((tag, index) => index !== i),
});
}
handleAddition(tag) {
this.setState(state => ({ tags: [...state.tags, tag] }));
}
handleDrag(tag, currPos, newPos) {
const tags = [...this.state.tags];
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, tag);
// re-render
this.setState({ tags: newTags });
}
handleTagClick(index) {
console.log('The tag at index ' + index + ' was clicked');
}
render() {
const { tags, suggestions } = this.state;
return (
<div>
<ReactTags
tags={tags}
suggestions={suggestions}
delimiters={delimiters}
handleDelete={this.handleDelete}
handleAddition={this.handleAddition}
handleDrag={this.handleDrag}
handleTagClick={this.handleTagClick}
/>
</div>
);
}
const suggestionsi = COUNTRIES.map((country) => {
return {
id: country,
text: country,
};
});
const delimiters = [KeyCodes.comma, KeyCodes.enter];
const SuggestTags = () => {
const [tags, setTags] = useState([{ id: "", text: "" }]);
const [suggestions, setSuggestions] = useState([{ id: "", text: "" }]);
setTags([
{ id: "Thailand", text: "Thailand" },
{ id: "India", text: "India" },
]);
const handleDelete = (i) => {
const { tags } = this.tags;
setTags(tags.filter((tag, index) => index !== i));
};
const handleAddition = (tag) => {
setTags({ tags: [...this.tags, tag] });
};
const handleDrag = (tag, currPos, newPos) => {
const tags = [...this.tags];
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, tag);
setTags(newTags);
};
return (
<div>
<ReactTags
tags={tags}
suggestions={suggestions}
handleDelete={handleDelete}
handleAddition={handleAddition}
handleDrag={handleDrag}
delimiters={delimiters}
/>
</div>
);
};
In your function component, it is currently setting state on every render.
// every render
setTags([
{ id: "Thailand", text: "Thailand" },
{ id: "India", text: "India" },
]);
Instead pass the initial tags into the useState initial value for tags, and only setTags when the data changes.
I hope to be descriptive, Let's say I have a Files Object Array
JSONfiledata = [
{
lastModified:123444,
name: 'file1',
size: 0,
type: ""
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: ""
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: ""
}
]
And I have a this component that receives that data through props
import React, {useState} from 'react'
const component = ({files}) => {
const [inputValue, setInputValue] = useState('')
const eventHandler = (e) => setInputValue(e.target.value)
const addNewKey = files.map(fileObj => Object.defineProperty(fileObj, 'newKey', {
value: inputValue
}))
return (
{
files.map(fileData => (<div>
{fileData.name}
<input value={inputValue} onChange={setInputValue} />
</div>))
}
)
}
How can I mutate the current files object and add a 'newKey' on each one depending on the inputValue, but independently from each other.
I mean, at position 0 let's say I write on the input "this is the file number one"
at position 1 "this is the file number two" and so on ....
At the end, the expected output will be
[
{
lastModified:123444,
name: 'file1',
size: 0,
type: "",
newKey: "this is the file number one"
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: "",
newKey: "this is the file number two"
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: "" ,
newKey: "this is the file number three"
}
]
I build a solution:
Build another component to manage every file individualy.
Like this:
import React, { useState } from 'react';
import { Map } from './Map';
export const MapList = ({ files }) => {
const [filesState, setFilesState] = useState([...files]);
const handleChange = nObject => {
/**You can compare with a unique id, preferably */
setFilesState(filesState => filesState.map(file => (file.name === nObject.name ? nObject : file)));
};
return (
<div>
{filesState.map(file => (
// If you have an ID you can send in this plance, to be more simple find the object in the handle function
<Map handleChange={handleChange} file={file} />
))}
<h2>Files Change</h2>
{filesState.map(file => (
<div>
<p>
{file.name} {file.newKey && file.newKey}
</p>
</div>
))}
</div>
);
};
In this wrapper component, you will update the entry array, with the handleChange function.
After you can build a component to manage your new key, for example:
import React, { useState } from 'react';
export const Map = ({ file, handleChange }) => {
const [input, setInput] = useState('');
const handleChangeKey = e => {
const { name, value } = e.target;
const nFile = { ...file, [name]: value };
setInput(value);
handleChange(nFile);
};
return (
<div>
<div>
<label htmlFor={file.name}>
<small>Input for: {file.name}</small>{' '}
</label>
<input id={file.name} name='newKey' value={input} onChange={handleChangeKey} type='text' />
</div>
</div>
);
};
It works for me, i think is a solution maybe not the best, but is a simple solutions.
const JSONfiledata = [
{
lastModified:123444,
name: 'file1',
size: 0,
type: ""
},
{
lastModified:123445,
name: 'file2',
size: 0,
type: ""
},
{
lastModified:123446,
name: 'file3',
size: 0,
type: ""
}
];
const fileNameToUpdate = 'file2';
const newKey = "file2Key";
const newArray = JSONfiledata.map((item) => {
if (item.name === fileNameToUpdate) {
return {...item, newKey: newKey };
} else {
return item;
}
});
console.log(`newArray==`, newArray);