set an array with useState doesn't change the result - javascript

I have a list of data like this:
data = [
{
id:1,
name: "boo"
},
{
id:1,
name: "boo"
}
]
I initialize it with react UseState like follows:
const [items, setItems] = useState(data)
And I map this in my react code and visualize them.
If I want to delete one I do it with the following function:
const deleteItem = async (id) => {
console.log('BEFORE- - - ->', items);
// this functions calls an api to delete the item
await deleteItem({ id });
setItems((oldItems) =>
oldItems.filter(({ id }) => id !== commentId)
);
console.log('AFTER- - - ->', items);
};
Graphically the component with the deleted item disappears, but actually the second console.log prints the same thing as the first log. In this case if I delete the element with id:1 I will get the same log while I see graphically that the element disappears. This causes a problem with indexing if I want to delete more than one item.
Which do you think is the problem? has I to do it into one useEffect?

Setting state is an asynchronous action. If you put a console outside of that function, on the next re-render you will see your state updated correctly (just like you've seen your item disappear visually).

This happens because setState is asynchronous so the state really changes after you console.log

Related

Why useEffect doesn't set items from local storage?

I've started to learn web development recently and this is making my head hurt. I made a simple todo app where you can add an item with id and value to an array and delete the item from the array. Now I want whenever the site refreshes, I want the items to be there. I made a testing array and for some reason, when refreshed, it works but not with data.
const [items, setItems] = useState([]);
const testarr = [{id: 1, value: "a"}, {id: 2, value: "b"}];
useEffect(() => {
const data = JSON.parse(localStorage.getItem("items"));
console.log(data);
// setItems(testarr);
setItems(data);
}, [])
useEffect(() => {
window.localStorage.setItem("items", JSON.stringify(items));
}, [items]);
What I came up with:
useEffect(() => {
const data = JSON.parse(localStorage.getItem("items"));
data.forEach(element => {
setItems(oldarr => [...oldarr, element]);
});
}, [])
The problem is, if I keep refreshing the site fast enough, eventually the array will become empty again. I don't understand, when refreshed it logs data just fine but when I want to setItems(data) that doesn't work. Does anyone know why it behaves like this?
setState is an asynchronous function, this implies that this code snippet below will always run with items as empty arrays before items receive data from "setItems(data)".
useEffect(() => {
window.localStorage.setItem("items", JSON.stringify(items));
}, [items]);
maybe, you can check if items isn't a empty array before store
useEffect(() => {
if (Array.isArray(items) && items.length > 0) {
window.localStorage.setItem("items", JSON.stringify(items));
}
}, [items]);
Please try localStorage without window Object
For example:
useEffect(() => {
localStorage.setItem("items", JSON.stringify(items));
}, [items]);
Issue
The below useEffect runs every time items changes, but also on mount. And on mount items is equal to [], that array given to useState in the beginning. That's why the localStorage is being set to [].
useEffect(() => {
window.localStorage.setItem("items", JSON.stringify(items));
}, [items]);
Solution
One way to solve this issue is to change your state definition as below, so when there is data in the localStorage you pick it:
const [items, setItems] = useState(!localStorage.getItem("items") ? [] : JSON.parse(localStorage.getItem("items")));
And at this point, you can remove those two useEffects you have there for getting data from the localStorage and giving it to setItems.

How to prevent adding duplicate objects into array in state in ReactJS?

I am fetching from an API that returns data in the form of an array. Lets say I have an array of 5 objects in this array which are exactly the same. I want to prevent duplicates by checking if duplicate exists before adding to my restaurants State. Below are snippets of my code:
const [restaurants, setRestaurants] = useState([])
const checkForDuplicateBusiness = (businessArray)=>{
businessArray.forEach(singleBusiness=>{
if(!restaurants.some(restaurant=> restaurant.id === singleBusiness.id)){
setRestaurants(restaurants=>[...restaurants, singleBusiness])
console.log(restaurants) //returns []
}
})
}
The problem is that when I am checking with this line
if(!restaurants.some(restaurant=> restaurant.id === singleBusiness.id))
the restaurants state is always empty. I understand that setState is async so the state is not updated by the time im checking it for the next iteration in the forEach loop. Im not sure what to do.
When I console log the restaurants via useEffect like:
useEffect(()=>{
console.log(restaurants)
},[restaurants])
it will show 5 identical objects.
[
0: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
1: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
2: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
3: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
4: {id: 'sDlYSTdgZCx0-3cRetKn8A'}
]
I am confused on this behavior because if i have exactly 5 copies of the object, that means my setRestaurants(restaurants=>[...restaurants, singleBusiness]) was working properly indicative from the spread operator. but my check if statement isnt?
The other solution I've thought of is to store my fetched data in a temporary array and perform the "preventDuplicate" logic in there, and then set it to my restaurants. But I'm not sure if this is the most efficient method. Is there a better way or the "React" way for this?
Any suggestions are appreciated. Thank you!
The setStateSetter function gives you the updated state. Use it higher:
function uniqueById(items) {
const set = new Set();
return items.filter((item) => {
const isDuplicate = set.has(item.id);
set.add(item.id);
return !isDuplicate;
});
}
const updateRestaurantsUnique = (newItems) => {
setRestaurants((restaurants) => {
return uniqueById([...restaurants, ...newItems]);
});
};

setState not updating the array in react

I am working on a react app where I have to change keys of objects in an array.Here's my code:
getInterfaces(){
//assign a array to interfaceOption state
let interfaceOptions=[
{
"interface_name": "ORU",
"interface_id": "1"
},
{
"interface_name": "MLM",
"interface_id": "2"
},
]
//change interfaceOptions keys to title and value
let interfaceOptionsWithTitle = interfaceOptions.map(function(item){
return {
title: item.interface_name,
value: item.interface_id
}
})
console.log(interfaceOptionsWithTitle)
this.setState({
interfaceOptions: interfaceOptionsWithTitle
})
//print updated interfaceOptions
console.log(this.state.interfaceOptions)
}
Here I initially assign a array,then I change its keys and print it using console.log and it prints the updated array but when I then setState the array and console log it again,it returns a empty array.Can someone help me to understand this?
Will this work if I want to update interfaceOptions?
this.state.interfaceOptions = this.state.interfaceOptions.map(item => {
return {
title: item.interface_name,
value: item.interface_id
};
});
Thanks
Becasue state only has new value when component re-render. So just put console.log to outside function getInterfaces to see the new value
getInterfaces(){
...
}
console.log(this.state.interfaceOptions)
setState is asynchronous in nature, you are trying to update the value and printing it in next line which will always give you the old state value because at the time when the browser tries to print the value the state is not updated, it takes some time to update the value.
If you want to print the updated state value you have pass a callback function to setState and print the value
e.g
this.setState({
interfaceOptions: interfaceOptionsWithTitle
},() => {
console.log(this.state.interfaceOptions)
})
Note: The callback function in the above setState will be executed after the rendering is completed.

How to assign first state data to second state in react?

When i am setting the data from one state to another state. It's saying undefined. Currently i am working on tinder like card swipe functionality with Right and left button click.
I am passing the user id from card to to button. Such as Right swipe and left swipe button.
//Scenario first
If have declared the array of object static, it's works like a charm, then it does not says any error.
////Scenario Second
If i am setting the data dynamically with API to SetState and assigning the state variable array data to another state variable, it says undefined.
I am trying to solve this issue from 3 days, but nothing worked, i am new in React js. Help would be appreciate.
Here is my code
const AllData= [
{
id: 1,
name: 'XYZ'
},
{
id: 2,
name: 'ABC'
},
{
id: 3,
name: 'ABC 2'
},
{
id: 4,
name: 'ABC 3'
},
{
id: 5,
name: 'ABC 4'
}
] //It works if set static array
const [AllData, setAllData] = useState([]); //it does not works
const GetAllUserData = async () =>{
const bodyParameters ={
session_id : SessionId
};
const {data : {data}} = await axios.post(GETALLUSER_API , bodyParameters);
setAllData(data);
}
// Setting current user from all data
const [currentUser, setCurrentUser] = useState(AllData[0])
console.log(currentUser); // undefined says
Here, AllData will be added to the state after the GetAllUserData is done executing, it is asynchronous function, so AllData will be available after some time, you have to update the currentUser you have to do like this.
useEffect(() => {
if (AllData.length) {
setCurrentUser(AllData[0]);
}
}, [AllData]);
It didn't worked because you may have returned before the setAllData could get called initialize AllData.
Being a api call it take some time to return data.
A good work around for this situation is to put a loader until it get the data and when you eventually receive the data then only render the content on the screen.

Add key/value pair to existing array of objects

I have an array of objects that is saved into a userList useState which is composed of:
[{
firstName: "blah"
lastName: "blah2"
}
{
firstName: "test"
lastName: "test2"
}]
I have a useEffect that calls a function and returns a value. I want to store a new key and value to each user in userList.
useEffect(() => {
userList.forEach((user, index) =>
returnNewValueForNewKeyFunction(user, index).then(newValue => {
userList[index]['newKey'] = newValue
//this console.log shows new field and value
console.log(userList)
//this console.log ALSO shows new field and value
console.log(JSON.stringify(contactList[index]))
})
)
}
}, [])
This is fine if I'm operating out of console.log, but unfortunately I need to render the data onto the page.. in my render I have:
return (
<TableBody>
{userList
.map((user, index) => (
<TableRow>
<TableCell>
{user.newKey}
</TableCell>
)
user.newKey is showing as blank and it seems like the user wasn't updated at all. How can I make it so the value is actually updated and can be read from when rendering?
You shouldnt mutate your list, you should use useState to store your list, so something like this :
const [ state, setState] = useState(userList);
Then when you want to update, do something like this :
const listCopy = [...state];
//Logic to update your list here
listCopy[index][otherindex] = Value;
setState(listCopy)
Hope this helps
You are modifying your userList but not calling your set function on which means React won't know to re-render with the updated state.
Instead of mutating the current state, you should create a new array and then call the set function returned by useState with the updated array after making your changes.
It also looks like your returnNewValueForNewKeyFunction is a promise / async which means each of your item changes are happening async. You'll need to make these synchronous / wait for them all before updating your state to make your state change a single update for the UI.
E.g., putting these both together - if you are doing:
const [userList, setUserList] = useState();
You could do:
useEffect(() => {
// Since can't use an async func directly with useEffect -
// define an async func to handle your updates and call it within the useEffect func
const updateUsers = async () => {
// Create a new array for your updated state
const updatedUserList = [];
// Loop over your values inline so your can await results to make them sync
for (let index = 0; index < userList.length; index ++) {
const user = userList[index];
const newVal = await returnNewValueForNewKeyFunction(user, index);
// Create a shallow copy of the original value and add the newValue
updatedUserList[index] = { ...user, newKey: newValue };
// ... Any other logic you need
}
// Call set with the updated value so React knows to re-render
setUserList(updatedUserList);
};
// Trigger your async update
updateUsers();
}, [])

Categories

Resources