This is kind of complicated to explain. I'm trying to make a toggle function that adds and deletes items on a different page with useContext. Everything adds and deletes fine until I tab to the other page, and that's where the error begins. Once I do that the function ignores what's in the array and will duplicate items in the array. What's odd about it is if I console.log or manually check the new item with the current array items it shows me everything. For example in order to add the new item to the array it checks if the index of new item is -1. If it is it will add the item and if not it will delete the item. However once I leave the page it doesn't see the item anymore and adds it anyway. If I console.log the item name and new item name, I can see both, and if I use === to check it also works fine until I switch tabs and then even though it's still console logging both names somehow it's still adding the item and ignoring that it already contains the item.
The code directory in my sandbox is src/Helpers/MyPicsContext. Here is the link to my sandbox codesandbox
The tabs on the website are Picks and the search page which you can access on Picks before any items are added or by clicking the magnifying glass in the top right of page.
And here is the actual code for the context page.
export const MyPicksContextProvider = props => {
const [picksList, setPicksList] = useState(
JSON.parse(localStorage.getItem('picksList'))
||
[]
)
//console.log(picksList)
useEffect(() => {
localStorage.setItem("picksList", JSON.stringify(picksList));
}, [picksList]);
const deleteCoin = coin => {
if (picksList.length === 1) {
setPicksList([]);
} else {
setPicksList(picksList.filter(list => {
console.log(list)
//console.log(coin)
return list !== coin;
}))
}
console.log('deleted');
console.log(picksList)
}
const toggleCoin = (coin) => {
if (picksList.length === 0) {
setPicksList([...picksList, coin]);
} else {
if (picksList.indexOf(coin) === -1 ) {
setPicksList([...picksList, coin]);
console.log('added 1')
} else {
deleteCoin(coin)
}
}
}
Perhaps I just don't understand useState and prevState, but I can't seem to find any examples that apply to what I'm trying to do here. It makes total sense in creating a counter or something simple like that.
The issue is that .indexOf checks for referential equality of items, same as using ===. This means that const a = {id: 'x'}; const b = a; console.log(a === b); will output true, but const a = {id: 'x'}; const b = {id: 'x'}; console.log(a === b); will output false.
In your situation, upon changing pages / refreshing, the state is reset and loaded from local storage. However, this creates new objects which are not referentially equal. Instead of using .indexOf you want to use .find, something like array.find((element) => element.id === newItem.id) to find the index of the item. You could also do your own deep equality check (confirming every field matches), but I suspect the ID alone is sufficient.
In fact, I would also recommend only keeping the array of "picks" as an array of string ID's. Then you can lookup the full data from your table for each of these ID's. Otherwise the current price will be stored in localStorage, and could be out of date.
Related
I want to ask , how to keep save the id's of the check boxes in a state , and whenever i switched back to first page it automatically search the element with id and mark check boxes automatically.
and if i unmark the checkbox , it deletes the id from the state.
i am able to think about the logic , but cant able to code,it
Small help ,will leads to solve this problem
While switching to other pages, i am succesfully saving the data ,by updating the state
`
// push all the unique objects (combination of previous state of selectedPayments and data from list)
setSelectedPayments((prevState) => {
var arr = [...prevState, ...list];
var newState = [
...new Map(arr.map((item) => [item.id, item])).values(),
];
return newState;
});
console.log('Selected payments are', selectedPayments);
`
Also , removing the objects , if again the checkbox is unchecked ,and updating the state
`
// pull all the objects , which got unChecked
setSelectedPayments((prevState) => {
var newState = prevState.filter(function (objFromA) {
return !list.find(function (objFromB) {
return objFromA.id === objFromB.id;
});
});
return newState;
});
`
Only facing issue with keeping track of the checked boxes, i have implimented this, this is keeping track of main(parent checkbox).
How to extract the ids saved and check the checkboxes when we naviagete from one page to another
`
let elementId = e.target.id;
if (selectedBoxes.includes(elementId)) {
const newArray = selectedBoxes.filter((e) => e !== elementId);
setSelectedBoxes(newArray);
} else {
setSelectedBoxes((prevState) => {
return [...prevState, elementId];
});
}
`
First i modified the Res Json , so that it set's a property isSelected = true,
by comparing the element from the selectedPayments
inAll check handler , i set the selectedPayments like this
And render using this
This is how ,i solved this problem.
** Better and improved answers are always welcome, please share your views.
I have updated this question with clearer and more concise code on 15/03/22.
I have a text filter and it filters an array (displaying a list) correctly if it matches something in the displayed list, but as the user deletes character by character the filter does not change. I need it to keep matching as the user delete's chars.
Here is the filter which is set as the onChange for the text field:
const [searchInputTitle, setSearchInputTitle] = useState<string>('');
const [filteredItemsArrayState, setFilteredItemsArrayState] = useState<IListItems[]>(props.requests);
const searchTitles = () => {
let filteredArrayByTitle: IListItems[] = [];
filteredArrayByTitle = theRequestsState.filter((item) => {
return item.Title && item.Title.toLowerCase().indexOf(searchInputTitle.toLowerCase()) >= 0;
});
console.log(searchInputTitle, 'searchInputTitle');
if (searchInputTitle && searchInputTitle.length > 0) {
setTheRequestsState(filteredArrayByTitle);
setIsFiltered(true);
} else if (searchInputTitle && searchInputTitle.length === 0) {
const AllItems = props.requests;
let sortedByID: IListItems[] = AllItems.sort((a, b) => a.Id > b.Id ? 1 : -1);
setTheRequestsState(sortedByID);
setIsFiltered(false);
}
};
<TextField
onChange={searchTitles}
value={searchInputTitle}
/>
useEffect(() => {
_onTitleFilterChange(null, searchInputTitle);
if (isFiltered == true) {
setFunctionalArea(null, null);
setRequestingFunction(null, null);
}
}, [isFiltered, searchInputTitle]);
////////
<DetailsList className={styles.DetailsList}
items={filteredItemsArrayState.slice((ListPage - 1) * 50, ((ListPage * 50)))}
/>
Can anyone see why the render is not updating on deletion of char and what I could use to do so?
Update: As I type a character into the search I can see it's finding the searched for char/word and also if I delete chars now it searches and finds actively, but as soon as I stop typing it reverts back to the original array of items.
Can you try to filter the array you give to the DetailsList so you never lost the data ..?
filteredItemsArrayState.filter(s => {
if (searchInputTitle.length === 0)
return true
else
return s.Title.toLowerCase().match(searchInputTitle.toLowerCase())
}).map(....)
Found the reason. Thanks for all your help.
The issue was the setIsFiltered(true); in the title filter function. Other filters were running based on this line and these other filters were recalling the unfiltered list everytime a key was pressed. I removed this line and the issue was fixed.
I have come to realise that useEffect is almost completely mandatory on most of my projects and is React hooks are quite a departure from the original React syntax.
I'm trying to show products depends on the selected category tab. The first tab is 'show all' and its categoryId ="" I change the categoryId when tab changes. I show products by using shownMenuItems state (shownMenuItems.map(...)), and get all items from api first in useEffect hook and store this data into menuItems state.
I need to set shownItems to an empty array before every tab changing. So I can get rid of previous tab's products. I used setShownMenuItems([]) but this doesn't work. Isn't it immutable operation? I create a new array and set it to shownMenuItems.
useEffect(() => {
setShownMenuItems([]) //every time the tab changes, items will be shown is supposed to be cleared, and be filled after below conditions
if(categoryId===""){ //show all products tab's categoryId = ""
setShownMenuItems(menuItems) //sets shownMenuItems to all menu items
}else{
menuItems.forEach(menuItem=>{
if(menuItem.categoryId === categoryId){ //searches all menu items, looking for ones category id's match
setShownMenuItems([...shownMenuItems, menuItem])
}
})
}
}, [categoryId])
I thought this part of code is enough. If it doesn't, tell me to share more codes. Thanks.
Solution:
useEffect(() => {
setShownMenuItems([]) //every time the tab changes, items will be shown is supposed to be cleared, and be filled after below conditions
if(categoryId===""){ //show all products tab's categoryId = ""
setShownMenuItems(menuItems) //sets shownMenuItems to all menu items
}else{
menuItems.forEach(menuItem=>{
if(menuItem.categoryId === categoryId){ //searches all menu items, looking for ones category id's match
// setShownMenuItems([...shownMenuItems, menuItem])
setShownMenuItems(prev=>[...prev, menuItem])
}
})
}
}, [categoryId])
React collect all set state hooks inside useEffect and apply them together. Split this logic to two useEffect.
Also, for prevent not necessary rerender You need to filter all items and only after that all of them set as new state.
useEffect(() => {
setShownMenuItems([]) //every time the tab changes, items will be shown is supposed to be cleared, and be filled after below conditions
}, [categoryId]) ;
useEffect(() => {
if(categoryId===""){ //show all products tab's categoryId = ""
setShownMenuItems(menuItems) //sets shownMenuItems to all menu items
} else{
const filtered = menuItems.filter(menuItem => {
return menuItem.categoryId === categoryId;
});
setShownMenuItems(filtered) ;
}
}, [categoryId])
I'm pretty new to vue/vuex/vuetify but starting to get the hang of it.
I have a problem though I haven't been able to solve properly.
I have an array of "projects" in my store. When deleting and adding items to the store via mutations the changes reflect properly in subcomponents referencing the array as a property.
However, changes to items in the array does not reflect even though I can see that the array in the store is updated.
The only way I got it to "work" with an update action was to either :
remove the project from the array in the store and then add it
use code that sort of does exactly the same as described above but like so:
state.categories = [
...state.categories.filter(element => element.id !== id),
category
]
But the problem with the above two methods is that the order of the array gets changed and I would really like to avoid that..
So basically, how would I rewrite my mutation method below to make the state reflect to subcomponents and keep the order of the array?
updateProject(state, project) {
var index = state.projects.findIndex(function (item, i) {
return item.id === project.id;
});
state.projects[index] = project;
}
You can use slice to inject edited project in correct position:
updateProject(state, project) {
var index = state.projects.findIndex(function(item, i) {
return item.id === project.id;
});
state.projects = [
...state.projects.slice(0, index),
project,
...state.projects.slice(index + 1)
]
}
or you can use JSON.parse(JSON.stringify()) to make a deep copy of object
updateProject(state, project) {
var index = state.projects.findIndex(function(item, i) {
return item.id === project.id;
});
state.projects[index] = project;
state.projects = JSON.parse(JSON.stringify(state.projects))
}
In essence, I have this working, I am building out a game component to allow users to select items they want to purchase, they get pushed into a pending array and then when they wish to check out I am pushing those objects from the pending array into the purchased array.
But something very odd is happening after that, the arrays look like it worked properly the pending array is empty and the purchased array has the correct items within it. The problem comes once I try and click a new item to put into the pending array if its the same item that is in the purchased array it alters the counter there.
I have been toying with different ways to try and fix this, but have no idea how it's affecting the purchased array without me setting the state again.
This is my code so far for operating this vendor functionality.
handleMerchantEquipment(item) {
let pending = this.state.merchantPending;
let found = Equipment.find(el => item === el.Name);
let check = pending.find(el => el.Name === found.Name);
if (!check) {
pending.push(found);
}
else {
var foundIndex = pending.findIndex(el => el.Name === check.Name);
check.Count +=1;
pending[foundIndex] = check;
}
let CP = [0];
let SP = [0];
let GP = [0];
let PP = [0];
pending.map(item => {
let cost = item.Cost * item.Count;
if (item.Currency === "CP") {
CP.push(cost);
}
else if (item.Currency === "SP") {
SP.push(cost);
}
else if (item.Currency === "GP") {
GP.push(cost);
}
else if (item.Currency === "PP") {
PP.push(cost);
}
});
let purchased = [];
this.state.merchantPending.map(item => {
purchased.push(item)
});
this.setState({
yourCP: totalCP,
yourSP: totalSP,
yourEP: totalEP,
yourGP: totalGP,
yourPP: totalPP,
purchased: purchased,
merchantPending: [],
});
Some of the code is related to the currency exchange which his working fine.
I have also tried the ... to update the array but the issue persisted with it updating the Count in the purchased state.
You are mutating state. You should not mutate state in React. You should always immutably change state in React.
I think the problem lies in this line.
let pending = this.state.merchantPending;
Change it to
let pending = [...this.state.merchantPending.map(obj => ({...obj}))];
Also, if you want to iterate an array, use forEach instead of map. map returns you a new array.
pending.forEach(item => {
...
}
What is that you want the purchasedArray to do when it encounters a duplicate entry or item? May you clarify that in your question?
If you want it to not allow duplicate items you have to first check the array to see if it contains that item. Pseudo code
if(contains) {
render alert
//or do something else
} else {
purchased.push(item)
}
I am not able to comment so I will delete this when you clarify.