REACT- My condition is working partially why? - javascript

In my json file from my api, I have a variable apply, If apply:0, Then It should should print my data and If apply:1, Then It should hide my data (i.e row in my Table). Actually when I combine my 2 conditions, It is partially working :(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) && (showGood ? menus : menus.filter((i) => i.taste !== "Good")), i.e Only the ...showGood ?... condition is working and the ...applyStatus?... not working.
Whereas if I do only :
<Table data={matchData && (applyStatus ? menus : menus.filter((i) => i.apply !== 1))> . Then, I have my data where apply:0 are displayed.
What's wrong in my code please ?
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus]=useState([])
const [showGood, setShowGood] = useState(false);
const [applyStatus, setApplyStatus] = useState(false);
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
useEffect (() => {
const GoodMenus = menus.filter((i) => i.taste === "Good");
const restOfMenus = menus.filter((i) => i.taste !== "Good");
setMenus([...GoodMenus, ...restOfMenus]);
}, [menus]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
show good
<Toggle value = {showGood} onChange={() => setShowGood(!showGood)} />
<Table
data={matchData &&(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) &&
(showGood ? menus : menus.filter((i) => i.taste !== "Good"))}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]

The problem is actually in the usage of your parenthesis. Your current logic looks like this.
(applyStatus ?
menus :
menus.filter((i) => i.apply !== 1)
) && (showGood ?
menus :
menus.filter((i) => i.taste !== "Good")
)
If you simplify it it looks like
(Condition ? array : filteredArray) && (Condition ? array : filteredArray)
If you simplify it again it results in
array && array
When you are using && operator on two array like you are doing it will return you the second array which in your case is the array filter on i.taste !== "Good".
I tried to show it with a code sample below
const array = [{price:5, age:10},{price:3, age:8},{price:9, age:12},{price:12, age:13}];
const ageFiltered = array.filter(x => x.age < 11);
const priceFiltered = array.filter(x => x.price > 4);
console.log("Age filtered:" + JSON.stringify(ageFiltered));
console.log("Price filtered:" + JSON.stringify(priceFiltered));
const result = ageFiltered && priceFiltered;
console.log("&& operator:" + JSON.stringify(result));
const doubleFiltered = array.filter(x => x.age < 11 && x.price > 4);
console.log("Dobule filters:" + JSON.stringify(doubleFiltered));
To fix your issue your code must be update to be like this
menus.filter(i => (applyStatus ? true : i.apply !== 1) && (showGood ? true : i.taste !== "Good") )

Your code is not working because && only return the value of the last operand. So i think your code should be this
<Table
data={
matchData && menus.filter(i => {
if (applyStatus) return true;
return i.apply !== 1;
}).filter(i => {
if (showGood) return true;
return i.taste !== 'Good'
})
}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>

if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
from my understanding menus is an array so there is nothing like menus.apply so its either you loop the menus checking if apply is true. then there is no need for
===0 just check if its true(1) or false(0).
if you want to display those that has apply you can sort them by using map just after fetching them like this
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
let data_ =res.data.menus
let_apply_true = []
let apply_false = []
data_.map((data)=>{
if(data.apply){
let_apply_true.push(data)
}
//else will return those that are false
let apply_false.push(data)
})
setApplyMenus(let_apply_true) //
setNotApplyMenus(let apply_false)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
//that is just one way
//

Related

How to set back to original state React.js

This is my codeSandbox Code! https://codesandbox.io/s/bold-lederberg-d2h508?file=/src/Components/Products/Products.js
Once I click in "default" I want to have the items back to the original presentation please! But I can not do it
sorting by price is working but the default option is giving me problems
import React, { useState } from "react";
import Products from "./Components/Products/Products";
import SearchInput from "./Components/SearchInput/SearchInput";
import data from "./utils/data";
const App = () => {
const [searchValue, setSearchValue] = useState("");
const [productsInfo, setProductsInfo] = useState([]);
const handleChange = (e) => {
setSearchValue(e.target.value);
};
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = data.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = data.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
const searchProducts = (products) => {
if (searchValue.toLowerCase().trim() === "") {
setProductsInfo(products);
} else {
const seekedItem = productsInfo.filter(
(product) =>
product.name.toLowerCase().trim().includes(searchValue) ||
product.category.toLowerCase().trim().includes(searchValue)
);
setProductsInfo(seekedItem);
}
};
return (
<div>
<SearchInput
handleChange={handleChange}
searchValue={searchValue}
selectedChangeFilter={selectedChangeFilter}
/>
<Products
data={data}
searchValue={searchValue}
productsInfo={productsInfo}
searchProducts={searchProducts}
/>
</div>
);
};
export default App;
I tried this one below but doesn't work
if (value === "all") {
const copiedData = [...data]
setProductsInfo(copiedData);
}
This is because while sorting you are sorting on your original data array. So the item ordering changes in it. You can copy your data into a seperate array and sort then as shown below.
Updated Codesandbox Link - https://codesandbox.io/s/fervent-napier-3rhvs4?file=/src/App.js
if (value === "lowest price") {
const productsToSort = [...data]
const lowestPriceGoods = productsToSort.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const productsToSort = [...data]
const highestPriceGoods = productsToSort.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
I think it's because data.sort() change your javascript data object, and doesn't return a new array like filter.
Try by replacing data.sort() by [...data].sort()
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
This is happening because Array.sort() method is mutable. You are sorting the data array and returning the sorted value. This is a short example of what is happening.
const fruits = ["Melon", "Avocado", "Apple"];
console.log(fruits) // ["Melon", "Avocado", "Apple"]
fruits.sort()
console.log(fruits) // [ 'Apple', 'Avocado', 'Melon' ]
In order to avoid that in your code you can destructure the data array. This will create a new array pointing it to a different place in the memory.
const selectedChangeFilter = (e) => {
const { value } = e.target;
// { . . . }
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
// { . . . }
}

Only my last condition is respected with reactjs

Hello I tried to put this condition in my data: data={filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")}
but only this part is working (displaying data where state !==Good)
matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
or if I do: data={filtersMenu} it works (allow me to filter via my dropdowns) my data content in my table.
So putting both conditions should allow me to display content where my state!==Good and (filtersMenu && matchData).
MenuDisplay component:
export default function MenuDisplay() {
const { menuId } = useParams();
const [menus, setMenus] = useState([]);
const [taste, setTaste] = useState([
{ label: "Good", value: "Good", name: "Good", selected: false },
{ label: "Medium", value: "Medium", name: "Medium", selected: false },
{ label: "Bad", value: "Bad", name: "Bad", selected: false }
]);
...
useEffect(() => {
myapi.menusFunction(parseInt(menuId))
.then(res => {
const menus = res.data.menus;
setMenus([
...menus.filter((i) => i.state === "Good"),
...menus.filter((i) => i.state !== "Good")
])
})
.catch(err => {
console.log(err)
})
}, [menuId]);
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const matchData = (
match.filter(({ _id }) => {
return !hidden[_id];
});
const data = [
...
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const reset = (list) =>
list.map((v) => ({
...v,
selected: false
}));
const cancel = () => {
setTaste(reset(taste))
...
};
const filtersMenu = matchData.filter((menu) => {
const allTaste = taste.every(i => i.selected === false)
const selTaste = (taste.filter((e) => e.selected)).map(({ value }) => value);
const allTasteSel = selTaste.indexOf(menu.taste) >= 0
...
return allTasteSel && ...
});
return (
<div>
...
<Table
data={filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")}
columns={data}
/>
</div>
);
}
I think this statement is logically wrong:
filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
Since when, filtersMenu && matchData && showGood is true, you want to show only the items which are filtered by your dropdown and are good, then, it should be like this:
filtersMenu && matchData && showGood ? filtersMenu.filter((i) => i.state === 'Good') : matchData
If the condition fails, you can either return matchData or filtersMenu, depending on your usecase.
Remove the brackets {} like below
columns=filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
i also think your logic is wrong
Try like this
columns=filtersMenu && matchData && showGood ? matchData.filter((i) => i.state !== "Good" : matchData)
is it just parenthesis around (filtersMenu && matchData && showGood) ? :
data={ ( filtersMenu && matchData && showGood ) ? matchData : matchData.filter((i) => i.state !== "Good") }

Need to fix a issue that comming in antd tree component

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"));

How to update a state variable correctly using onClick in functional component

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>
);
}

how to add multiple objects in reactjs?

I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues

Categories

Resources