Only rerender and fetch when api property has changed - reactjs

Im trying to get the use effect to rerender only when the labels property has changed.
i want to fetch newest changes in the labels property only when there is a change.
My code below:
const usePrevious = (value: any) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, value)
return ref.current
}
const prevLabels = usePrevious(labels)
useEffect(() => {
if (labels !== prevLabels) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
const fetchUpdatedLabels = async () => {
await axios
.get(`/events/${printEvent.id}`)
.then((response) => {
const newLabels = response.data.labels
// filter response to empty array if there are no splits left
// if is null return empty string
let fetchedLabels =
newLabels !== null
? newLabels
.toString()
.split(',')
.filter((label: string) => {
return label !== ''
}) || ''
: ''
getLabels(fetchedLabels)
print.updateEvent(printEvent.id, 'value', printEvent.value)
})
.catch((err) => {
console.error(err, 'cant fetch labels from api')
})
}
it keeps refetching nonstop, how do i achieve this?

This is most likely in part because you are comparing an array with an array using the equivalence operator !==. It can be surprising to people new to JS but if you do this in a JS console:
[1,2,3] === [1,2,3]
It returns false. The reason is that an array is an object and what you are asking is "is the array on the left literally the same as the one on the right" -- as in the same variable. Whereas these are 2 separate array instances that happen to have the same contents. You might wonder why it works then on strings and numbers etc, but that's because those are primitive values and not objects which are instantiated. It's a weird JS gotcha.
There are several ways to compare an array. Try this:
useEffect(() => {
if (labels.sort().join(',') !== prevLabels.sort().join(',')) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
I'm not sure about your use case here though as when the labels are different, you fetch them, but the only way they can change is to be fetched? Is there something else in the code that changes the labels? If so, won't they get wiped out by the ones from the server now as soon as they change?
Something else is wrong here I think. Where do you set labels into state?

Related

cant map fetched data in react js (Cannot read properties of undefined (reading 'map'))

I have fetched data from spotify api and it displays in console but when i try to return data inpage it says map is not defined I use useState([]) and pass it to array if I am saying it right way
const [track, setTrack] = useState([])
const getRecommendation = async (e) => {
e.preventDefault()
const {data} = await axios.get("https://api.spotify.com/v1/recommendations", {
headers: {
Authorization: `Bearer ${token}`
},
params: {
limit: '10',
seed_artists: '4NHQUGzhtTLFvgF5SZesLK',
seed_genres: 'rock,pop,metal',
seed_tracks: '0c6xIDDpzE81m2q797ordA'
}
})
setTrack(data.tracks.item)
console.log(data);
}
const renderTracks = () => {
return track.map(tracks => {
return (
<h1 className='track-name'>
{tracks.name}
</h1>
)
})
}
here in console
anu advices? Thanks
According to this:
console.log(data);
The data object has a property called tracks which is an array. Which means this:
setTrack(data.tracks.item);
Is setting the state to undefined, because there is no .item property on an array.
This implies that you're expecting to iterate over an array:
return track.map(tracks => {
So set the state to the array, not some property you expect to be on the array:
setTrack(data.tracks);
As an aside, the plurality in your names is backwards. This is going to cause confusion for you (if it hasn't already). Consider the semantics of this:
return track.map(tracks => {
In this line of code, track is an array and tracks is a single object. That's not how plurality works. Rename your variables to accurately reflect what they contain:
const [tracks, setTracks] = useState([]);
and:
setTracks(data.tracks);
and:
return tracks.map(track => {
This could very well be what caused your problem in the first place, since setTrack implies that it's expecting a single object and data.tracks is an array.
Keep your variable names consistent and meaningful and your code will be much easier to read and understand.

TypeError: Cannot assign to read only property 'x' of object '[object Array]'

I've tried to change this so many ways, no matter how it's done/explained on other Stack Overflow posts. I have an array of objects, and I am trying to map/loop through the array move one of the objects to the front of the array.
Here's what I've got:
const [users, setUsers] = useState<User[]>([]);
const [primaryUser, setPrimaryUser] = useState<User>({
id: 0;
first_name: "",
last_name: ""
})
useEffect(() => {
users.map((user, i) => {
if(user.id === 12345) {
users.splice(i, 1);
users.unshift(user)
setPrimaryUser(user);
}
});
setUsers(users);
}, [])
Whether I map as shown above or use a for loop, I always end up getting the following error: TypeError: Cannot assign to read only property 'x' of object '[object Array]'
Any suggestions/help is greatly appreciated. Thanks in advance!
I see the code is used in React, so it is best to avoid modifying the users array in place (for example with .unshift() or .splice()) as React will not see the difference and will not re-render.
It is common practice in React to create new value for the previous one.
I think you can achieve the goal by filtering the users array twice (filter method creates a copy without affecting the original):
users with id 12345 - expecting just one and and store it in primaryUser variable
users with id not 12345
and combining those arrays:
useEffect(() => {
const primaryUserID = 12345;
const [ primaryUser ] = users.filter(user => user.id === primaryUserID)
setPrimaryUser(primaryUser);
// Construct array from 'primaryUser' and the rest of users
const newUsers = [
primaryUser,
...users.filter(user => user.id !== primaryUserID),
];
setUsers(newUsers);
}, [])
Thanks to both VLAZ and Vitalii (great article), I was able to get it working. Here was my solution:
useEffect(() => {
let tempUsers = [...users];
tempUsers.map((user, i) => {
if(user.id === 12345) {
tempUsers.splice(i, 1);
tempUsers.unshift(user)
setPrimaryUser(user);
}
});
setUsers(tempUsers);
}, [])
THANK YOU!

Why doesn't a useEffect hook trigger with an object in the dependency array?

I'm noticing something really strange while working with hooks, I've got the following:
import React, { useState, useEffect } from "react";
const [dependency1, setDependency1] = useState({});
const [dependency2, setDependency2] = useState([]);
useEffect(() => {
console.log("dependency 1 got an update");
}, [dependency1]);
useEffect(() => {
console.log("dependency 2 got an update");
}, [dependency2]);
setInterval(() => {
setDependency1(prevDep1 => {
const _key = "test_" + Math.random().toString();
if (prevDep1[_key] === undefined) prevDep1[_key] = [];
else prevDep1[key].push("foo");
return prevDep1;
})
setDependency2(prevDep2 => [...prevDep2, Math.random()]);
}, 1000);
for some reason only the useEffect with dependency2 (the array where items get added) triggers, the one with dependency1 (the object where keys get added) doesn't trigger..
Why is this happening, and how can I make it work?
setInterval(() => {
setDependency1(prevDep1 => {
const _key = "test_" + Math.random().toString();
return {...prevDep1, [_key]: [...(prevDep1[_key] || []), 'foo'] }
})
setDependency2(prevDep2 => [...prevDep2, Math.random()]);
}, 1000);
State should be updated in an immutable way.
React will only check for reference equality when deciding a dependency changed, so if the old and new values pass a === check, it considers it unchanged.
In your first dependency you simply added a key to the existing object, thus not changing the actual object. The second dependency actually gets replaced altogether when spreading the old values into a new array.
You're returning an assignment statement here:
setDependency1(prevDep1 => prevDep1["test_" + Math.random().toString()] = ["foo"]);
You should return an object. Maybe something like:
setDependency1(prevDep1 => ({ ...prevDep1, ["test_" + Math.random().toString()]: ["foo"] }));

ReactJs UseState : insert element into array not updating

I am trying to use React Hooks but somehow my state is not updating. When I click on the checkbox (see in the example), I want the index of the latter to be added to the array selectedItems, and vice versa
My function looks like this:
const [selectedItems, setSelectedItems] = useState([]);
const handleSelectMultiple = index => {
if (selectedItems.includes(index)) {
setSelectedItems(selectedItems.filter(id => id !== index));
} else {
setSelectedItems(selectedItems => [...selectedItems, index]);
}
console.log("selectedItems", selectedItems, "index", index);
};
You can find the console.log result
here
An empty array in the result, can someone explain to me where I missed something ?
Because useState is asynchronous - you wont see an immediate update after calling it.
Try adding a useEffect which uses a dependency array to check when values have been updated.
useEffect(() => {
console.log(selectedItems);
}, [selectedItems])
Actually there isn't a problem with your code. It's just that when you log selectedItems the state isn't updated yet.
If you need selectedItems exactly after you update the state in your function you can do as follow:
const handleSelectMultiple = index => {
let newSelectedItems;
if (selectedItems.includes(index)) {
newSelectedItems = selectedItems.filter(id => id !== index);
} else {
newSelectedItems = [...selectedItems, index];
}
setSelectedItems(newSelectedItems);
console.log("selectedItems", newSelectedItems, "index", index);
};

What is proper way to call function from onClick so it don't trigger wrong one?

When I click on button then onClick triggers correct function, run half through and jumps to other function which is not related to it and run through half of it and jumps back to first function, runs half trough again and drops error
Uncaught TypeError: _this.state.searchValue.toLowerCase is not a function
Interesting part is that I click other button before which triggers this function with toLowerCase() and there is no errors.
I dont have any idea whats going on here but so far i was trying to remove few lines to see which line cause it because I dont think that line with toLowerCase() realy is the reason. Everything works when I remove lines where is first this.setState.
Here is my function:
( Alerts is used to track where function is at, that how i know
that it run half through only. It never reach alert("DDD").
This function is which is triggered with button onClick like it should be )
onSelect = (e) => {
const data = e.target.getAttribute('data-id');
const itemId = e.target.getAttribute('data-id');
const itemIdState = !this.state[e.target.getAttribute('data-id')];
alert("AAA")
this.setState(state => { // <--- Somehow problem comes from this setState function
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
alert("CCC")
this.setState(State => ({
[itemId]: itemIdState,
}), function() {
alert("DDD")
if(this.state[itemId] === true){
this.setState({isAnySelected: true})
}else if(this.state[itemId] === false){
this.setState({isAnySelected: false})
}
})
}
This is other function which is triggered by mistake and is not related to other. It is just returning component which is displayed and when I press on its button then i have this issue.
filterSearch = (id, title, path) => {
let name = title.toLowerCase()
let filter = this.state.searchValue.toLowerCase()
if(name.includes(filter)){
return <SearchResult key={id} data-id={id} pName={path} onClick={this.onSelect} selected={this.state[id]} />
}
}
And here is from where filterSearch is triggered. Behind this.props.searchResult is Redux.
{this.props.searchResult ? this.props.searchResult.map(category =>
this.filterSearch(category.id, category.title, category.path)
) : null
}
I think I see what the problem is: in your problematic this.setState, you cast everything in your state to a boolean:
this.setState(state => {
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
Your for() statement ends up comparing searchValue to data (some kind of ID), which I imagine more often than not will not be the case, so searchValue ends up getting set to false.
And what happens when you try to do .toLowerCase() on a Boolean?
To fix this, consider structuring your state like this:
this.state = {
searchValue: '',
ids: {},
};
Then, replace your problematic this.setState with something like this:
this.setState((state) => {
const newIDs = {
// Create a clone of your current IDs
...state.ids,
};
Object.keys(newIDs).forEach(key => {
newIDs[key] = key === data
});
alert("BBB")
return {
// searchValue will remain untouched
...state,
// Only update your IDs
ids: newIDs,
}
});
What exactly are you wanting to do here?
this.setState(state => {
const newState = {}; // You are initializing an object
for (const dataId in state) {
newState[dataId] = dataId === data // You are putting in an array every property of state that is equal to data
}
return newState
});
So irrevocably, your this.state.searchValue property will be changed to something else, which is of boolean type. So toLowerCase being a function for string.prototype, you will get an error.
You should describe what you where aiming to get here.

Resources