what my issue is
Scenario 1: It is not opening sub-lists after searching or removing any list name from the search bar
Scenario 2: After searching any list name in the search bar that is already selected, then after searching that selected list it is showing that list but its checkbox is not selected.
so what do I need after searching list name in the search bar if that list has a sub-list for example if I New Watchlists then I want to show that sub-list also that present under this list after searching in the search bar but right it coming with empty list you can see image below..
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;
}
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 = React.useCallback(
(checkedKeysValue, e) => {
if (e.checked) {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys,
_.cloneDeep([
...e.node.key,
...e.node.children.map((child) => child.key)
])
)
);
} else {
setCheckedKeys(_.union(checkedKeys, [e.node.key]));
}
} else {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys.filter((item) => {
return (
item !== e.node.key &&
!e.node.children.filter((child) => child.key === item).length
);
})
)
);
} else {
setCheckedKeys(
_.cloneDeep(checkedKeys.filter((item) => item !== e.node.key))
);
}
}
},
[checkedKeys, setCheckedKeys]
);
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);
}, []);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
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
When you run the filterTreeData function, you assign the children to only the tree that contains the search term. So, you're searching n levels deep for the term. Notice that it works like I'd expect it to if you only search one level deep. That is, if the node has children, you return the node as such rather than filtering down all the children that don't also contain the term. when you filter with the string "new" in your example, you'll notice that none of the children in the collection "New Watchlists" contain the term and they are therefore filtered out leaving you with an empty array. My naive solution is to just return the children as in my following example:
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: n.children
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
I call this solution naive, because I'm not exactly sure what your use case is. There might be more conditional logic you need to render in there for first searching n levels deep for the term and then and returning the whole node if the term is discovered anywhere inside the node or children.
Related
I have a problem with the localStorage in my application. When I add items to a list of "favorites" they are stored without any problem in the localStorage, they can even be deleted by clicking them again.
But when I refresh the page, my application doesn't read that these items are in the favorites list and therefore doesn't mark them. Also, when I add a new item to the favorites list it causes it to delete everything from localStorage and start over.
Here's a gif of the localStorage view
Here's the code:
import React, { useState, useEffect } from 'react';
import SearchBar from '../../SearchBar/SearchBar.js';
import FiltersBox from '../FiltersBox/FiltersBox.js';
import { getItems } from '../../../Database/Database.js';
import './ItemsContainer.css';
function ItemsContainer() {
const [items, setItems] = useState([]);
const [search, setSearch] = useState('');
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
const [sortPrice, setSortPrice] = useState('');
const [filterCategory, setFilterCategory] = useState('');
const addItemToFavorites = item => {
let existentItem = favoriteItems.find(favItem => favItem.id === item.id);
if (existentItem) {
let filterTheExistentItem = favoriteItems.filter(
favItem => item.title !== favItem.title
);
setFavoriteItems(filterTheExistentItem);
let stringItems = JSON.stringify(filterTheExistentItem);
localStorage.setItem('Favorite Items', stringItems);
} else {
setFavoriteItems([...favoriteItems, item]);
let stringItems = JSON.stringify([...favoriteItems, item]);
localStorage.setItem('Favorite Items', stringItems);
}
};
const filteredItemsList = () => {
let newItemList = [];
newItemList = items.filter(item => {
if (filterCategory !== '' && filterCategory !== 'none') {
return item.category === filterCategory;
} else {
return item;
}
});
if (sortPrice === 'ascending') {
return newItemList.sort((a, b) => (a.price > b.price ? 1 : -1));
} else if (sortPrice === 'descending') {
return newItemList.sort((a, b) => (b.price > a.price ? 1 : -1));
} else {
return newItemList;
}
};
function onSortSelected(sortValue) {
setSortPrice(sortValue);
}
function onCategorySelected(categoryValue) {
setFilterCategory(categoryValue);
}
useEffect(() => {
getItems().then(res => setItems(res));
}, []);
useEffect(() => {
let xd = JSON.parse(localItems);
console.log(xd);
}, [localItems]);
return (
<div>
<SearchBar setSearch={setSearch} />
<FiltersBox
items={items}
setItems={setItems}
onSortSelected={onSortSelected}
onCategorySelected={onCategorySelected}
/>
<div>
{filteredItemsList()
.filter(item =>
search.toLowerCase() === ''
? item
: item.title.toLowerCase().includes(search)
)
.map(item => (
<div key={item.id}>
<div>{item.title}</div>
<button
className={favoriteItems.includes(item) ? 'si' : 'no'}
onClick={() => addItemToFavorites(item)}>
Add to favorites
</button>
</div>
))}
</div>
</div>
);
}
export default ItemsContainer;
And here I leave a GIF with a continuous console.log of the localStorage:
I tried everyrhing, and I don't know what is happening.
You're retrieving your items in localItems and... you do nothing with this variable. You should initialize your state favoritesItems with your local storage
const getItemsFromLocalStorage = () => {
const items = localStorage.getItem('Favorite Items');
return items ? JSON.parse(items) : [];
}
const [favoriteItems, setFavoriteItems] = useState(getItemsFromLocalStorage())
This is where the culprit is:
const [favoriteItems, setFavoriteItems] = useState([]);
let localItems = localStorage.getItem('Favorite Items');
You load localStorage into localItems, but you expect it to be in favoriteItems, where you have never assigned it. You would need to specify the item of localStorage as the initial state, like:
let localItems = localStorage.getItem('Favorite Items');
const [favoriteItems, setFavoriteItems] = useState(localItems ? localItems : []);
Can anybody help me with this issue? I have a state called "filteredPokemon" which fetches a list of pokemon based on some things, then I pass it to this function called PokemonDisplayArea where I proceed to display the list of pokemon. However, when I change offset I expect the behavior to ADD onto the previous state of "Cards" which are the element rendering the HTML, however instead, it grabs the newest filteredpokemon and appends it twice. Any help would be appreciated!
Here is a video
https://clipchamp.com/watch/EcMJbOMjOaL
The code:
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
let cardsSkele = [];
if (filteredPokemon.length > 0) {
for (let i = 0; i < filteredPokemon.length; i++) {
if (undefined !== filteredPokemon[i].name) {
cardsSkele.push(<PokemonCard key={filteredPokemon[i].id} name={filteredPokemon[i].name}></PokemonCard>);
cardsSkele.sort((a, b) => a.key - b.key)
}
}
setCards(prevArray => [...prevArray, ...cardsSkele]);
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon pokemons={pokemons} amount={20} offset={offset} filteredPokemon={filteredPokemon} setFilteredPokemon={setFilteredPokemon}></GetFilteredPokemon>
{cards}
</div>
)
}
Any help would be appreciated, I am a beginner, thanks!
I tried passing different states into useEffect, and tried console logging the data but the data seems to change fine, and the list just duplicates.
For starters, I would just store data in the state rather than components itself as it easy to reason about and transform.
Iterate over the cards and update the data for existing items or add the new item. Something along these lines.
function getUpdatedCardsData(prevCards, filteredPokemon) {
// get list of all the ids for the filtered pokemons
const filteredPokemonIds = filteredPokemon.map(fp => fp.id);
// This will combine the data for existing cards that are part of
// filteredPokemon and maintain a map of ids that are new and need to be added to cards
const updatedCards = prevCards.reduce((memo, card) => {
const cardId = card.id;
const isNewCard = !filteredPokemonIds.includes(cardId);
// add it to the map if new
if(isNewCard) {
memo.newCardIds.push(cardId);
} else {
// find the card that is in filtered pokemon
const found = filteredPokemon.find(fp => fp.id === cardId);
if(found) {
const updatedCard = {
...card,
...found
};
memo.updatedCards.push(updatedCard);
}
}
return memo;
}, {
updatedCards: [],
newCardIds: []
});
}
function getNewCards(newCardIds, filteredPokemon) {
// List of the new cards
const newCards = newCardIds.reduce((memo, newCardId) => {
const found = filteredPokemon.find(fp => fp.id === newCardId);
if(found) {
memo.push(found);
}
return memo;
}, []);
}
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
if(filteredPokemon) {
setCards(prevCards => {
const {
updatedCards,
newCardIds
} = getUpdatedCardsData(prevCards, filteredPokemon);
const newCards = getNewCards(newCardIds, filteredPokemon);
// add updated and new and sort them
const sortedCards = [...updatedCards, ...newCards].sort((a, b) => a.id - b.id);
return sortedCards;
});
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon
pokemons={pokemons}
amount={20}
offset={offset}
filteredPokemon={filteredPokemon}
setFilteredPokemon={setFilteredPokemon}
/>
{cards.map((card, i) => {
const pokemon = card[i];
return (
<PokemonCard
key={pokemon.id}
name={pokemon.name}
/>
})}
</div>
)
}
i am doing some filtering on items based on ids which is in selection object. and then evaluating true or not based on its completion true or false from items which is an array of objects.
below is my code,
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
let noJobs;
if (selection) { //how to put this block in useMemo
const filteredItems = items.filter(item =>
Object.keys(selection).includes(item.id)
);
noJobs = filteredItems.map(item => item.jobs.map(job => job.completed))
.flat().every(Boolean);
}
return (
<button disabled = {noJobs}> Click me </button>
);
}
How can i put the block which includes noJobs calcualtion (from if(selection)) to React.useMemo. could someone help me with this. I am learning react and hooks is new to me.
thanks.
Try followings it:
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
const noJobs = React.useMemo(() => {
if (selection) {
const filteredItems = items.filter(item => {
return Object.keys(selection).includes(item.id)
});
return filteredItems.map(item => item.jobs.map(job => {
return job.completed
}))
.flat()
.every(Boolean);
};
return undefined; //or everything you want
} , [items , selection]);
return (
<button disabled = {noJobs}> Click me </button>
);
}
I have a set of card objects that I map over.
When I click on a card it adds the selected class which in turn gives it a border to show the user it is selected, it also adds the id of the card to the selectedCards useState array.
WHAT I WANT TO HAPPEN:
Each card object has a creditAvailable key state which is equal to a figure.
On selection (click) of the card, in addition to selecting the card I would also like to add up the creditAvailable and display it on the screen. and when I unselect the card I would like the figure to go down.
WHAT I HAVE TRIED:
I thought it would be as simple as calling the function to add up the credit inside the first function which selects the card, however when console logging inside the first function I see that the state has not yet updated. (scope).
I then tried to call the function outside of the first function but it gave me an infinite loop. Here is my code.
Any ideas? Thanks
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const [total, setTotal] = useState();
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
// addUpCreditAvailable(); // nothing happens
console.log(selectedCards); // []
};
console.log(selectedCards) // [1] for example. This is in the global scope
const addUpCreditAvailable = () => {
console.log("inside add up credit");
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
setTotal(result);
return result;
};
return (
<div className="Container">
<UserInputForm submitData={handleSubmitData} />
<h1> Cards available to you displayed are below!</h1>
{cards.map(
({
id,
name,
number,
apr,
balanceTransfer,
purchaseDuration,
creditAvailable,
expiry,
}) => (
<CreditCard
key={id}
name={name}
number={number}
apr={apr}
balanceTransferDuration={balanceTransfer}
purchaseOfferDuration={purchaseDuration}
creditAvailable={creditAvailable}
expiry={expiry}
onClickCard={() => handleSelectCard(id)}
selected={selectedCards.includes(id)}
/>
)
)}
<span> £{total}</span>
)}
I figured it out with the help from above. As Wilms said i had to return the result of the handleSelectCard function and return the result of the addUpCredit function. Then I called the addUpCreditAvailable with the selectedCards state and stored the result in a variable which i then displayed in my render method.
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
return selectedCards;
};
const addUpCreditAvailable = (selectedCards) => {
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
return result;
};
const totalCredit = addUpCreditAvailable(selectedCards);
render method:
render (
[...]
{selectedCards.length && (
<div className={bem(baseClass, "total-credit")}>
Total Credit available: £{totalCredit}
</div>
)}
[...]
)
I have a Redux Reselect selector that works fine:
// in reselect1.js
const getInputs = state => state.inputs;
export default createSelector(
getInputs,
inputs => {
const { thing } = inputs;
const items = inputs.items
.map(item => item.text)
.filter(item => item);
return {
thing,
items
};
},
);
In another selector it works if I repeat the logic to get the inputs without IDs in inputsWithoutIds:
// in reselect2.js
const inputsWithoutIds = state => {
const { thing } = state.inputs;
const items = state.inputs.items
.map(item => item.text)
.filter(item => item);
return {
thing,
items
};
};
const getSaved = state => state.saved;
export default createSelector(
getSaved,
inputsWithoutIds,
(saved, inputs) => {
const isSaved = saved.filter(saved => {
return _isEqual(saved, inputs);
}).length;
return {
isSaved,
};
},
);
However if I try to reuse my first selector then when state.inputs.items changes the new value is not picked up. So the value of isSaved doesn't change when it should.
// in reselect2.js
import inputsWithoutIds from './reselect1'
const getSaved = state => state.saved;
export default createSelector(
getSaved,
inputsWithoutIds,
(saved, inputs) => {
const isSaved = saved.filter(saved => {
return _isEqual(saved, inputs);
}).length;
return {
isSaved,
};
},
);
I can see in the docs that these functions are composable so Im not sure what I'm doing wrong? https://github.com/reduxjs/reselect