useState React hook always returning initial value - reactjs

locationHistory is always an empty array in the following code:
export function LocationHistoryProvider({ history, children }) {
const [locationHistory, setLocationHistory] = useState([])
useEffect(() => history.listen((location, action) => {
console.log('old state:', locationHistory)
const newLocationHistory = locationHistory ? [...locationHistory, location.pathname] : [location.pathname]
setLocationHistory(newLocationHistory)
}), [history])
return <LocationHistoryContext.Provider value={locationHistory}>{children}</LocationHistoryContext.Provider>
}
console.log always logs []. I have tried doing exactly the same thing in a regular react class and it works fine, which leads me to think I am using hooks wrong.
Any advice would be much appreciated.
UPDATE: Removing the second argument to useEffect ([history]) fixes it. But why? The intention is that this effect will not need to be rerun on every rerender. Becuase it shouldn't need to be. I thought that was the way effects worked.
Adding an empty array also breaks it. It seems [locationHistory] must be added as the 2nd argument to useEffect which stops it from breaking (or no 2nd argument at all). But I am confused why this stops it from breaking? history.listen should run any time the location changes. Why does useEffect need to run again every time locationHistory changes, in order to avoid the aforementioned problem?
P.S. Play around with it here: https://codesandbox.io/s/react-router-ur4d3?fontsize=14 (thanks to lissitz for doing most the leg work there)

You're setting up a listener for the history object, right?
Assuming your history object will remain the same (the very same object reference) across multiple render, this is want you should do:
Set up the listener, after 1st render (i.e: after mounting)
Remove the listener, after unmount
For this you could do it like this:
useEffect(()=>{
history.listen(()=>{//DO WHATEVER});
return () => history.unsubscribe(); // PSEUDO CODE. YOU CAN RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[]); // THIS EMPTY ARRAY MAKES SURE YOUR EFFECT WILL ONLY RUN AFTER 1ST RENDER
But if your history object will change on every render, you'll need to:
cancel the last listener (from the previous render) and
set up a new listener every time your history object changes.
useEffect(()=>{
history.listen(()=>{//DO SOMETHING});
return () => history.unsubscribe(); // PSEUDO CODE. IN THIS CASE, YOU SHOULD RETURN A FUNCTION TO CANCEL YOUR LISTENER
},[history]); // THIS ARRAY MAKES SURE YOUR EFFECT WILL RUN AFTER EVERY RENDER WITH A DIFFERENT `history` OBJECT
NOTE: setState functions are guaranteed to be the same instance across every render. So they don't need to be in the dependency array.
But if you want to access the current state inside of your useEffect. You shouldn't use it directly like you did with the locationHistory (you can, but if you do, you'll need to add it to the dependency array and your effect will run every time it changes). To avoid accessing it directly and adding it to the dependency array, you can do it like this, by using the functional form of the setState method.
setLocationHistory((prevState) => {
if (prevState.length > 0) {
// DO WHATEVER
}
return SOMETHING; // I.E.: SOMETHING WILL BE YOUR NEW STATE
});

Related

useEffect() in my search Bar component causes an infinite loop, that I cannot understand

I'm new to React-Native and am following along in a course on react-native.
This git hub links to a my repository the code has the problem(infinite loop) I describe in the following question.
I have spent 12+ hours trying to figure this out. Please help me figure this out if you can.
https://github.com/JohnyClash/mealsToGo
useEffect in question
above photo directory: 'src/features/restaurants/components/search.components.js'
useEffect(() => {
search(searchKeyword);
}, []);
The above code creates a feedback loop that causes the app to continuously fetch from a mock api, that returns the location information, loads to the screen and then quickly reloads ad infinitum.Its intended purpose is to run a single time on component mount to cause a single default search. Instead, This useEffect() inside of search.component runs its callback repeatedly. The useEffect is not tracking a dependency that has changed, and is given [] an empty array in place of dependency
useEffect(callback,dependencies)
useEffect(callback,[])
Shouldn't this syntax of useEffect only run once after its mount, and not run again if something is updated? If my understanding is correct how is it possible that this use effect is running in an infinite loop?
this useEffect() is the culprit as the infinite reload loop stops when it is this useEffect() is removed.
all other functionality down this logic chain does not create an infinite loop, as in the search method initiated through onSubmitEditing works well without looping.
The problem of this infinite loop is being caused by this location object here, probably because every time the LocationContext is rerendered (a search is done or state is updated), it creates a new instance of the location object, which makes the useEffect be called again, then it search again, recreates the location object when calling useEffect again, which makes a new search, update some state and recreates the location object...
Code with problem of infinite loop:
useEffect(() => {
console.log(location)
if (location) {
const locationString = `${location.lat},${location.lng}`;
retrieveRestaurants(locationString);
}
}, [location]);
If you do something like this, might solve this problem:
useEffect(() => {
if (location?.lat && location?.lng) {
const locationString = `${location.lat},${location.lng}`;
retrieveRestaurants(locationString);
}
}, [location?.lat, location?.lng]);
Also be careful with setTimeout, and not clearing it on component unmount
Tip: Always avoid object, array or function in useEffect dependency, and if necessary it needs to be memorized with useMemo or useCallback
Try This :
useEffect(() => {
search(keyword)
}, [keyword])

React "interval" has always the same state

I am using react 16.10 with typescript.
I have this code:
const [state, setState] = useState<State>({
test: 1
});
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, []);
function timer() {
if(state.test === 3){
//HE WILL NEVER ENTER THIS CODE FUNCTION
}
setState({...state, test: 3}); // Next time we should have the value 3, BUT IT HAS NEVER THIS VALUE?!?!
}
return (
<>
<span>The output is {state.test}</span> //First it is showing 1, after 5 seconds 3. Working great
</>
);
I am changing the value of test to the number 3 in the interval "timer". setState is working fine. I can see the value in my component, seeing the number switching from 1 to 3.
But in the timer function the value is never changed. It has every time the default value of 1.
What I am doing wrong?
You need to add dependency to useEffect
//On component mount we start our interval
useEffect(() => {
const timerID = setInterval(timer, 5000); //every 5 seconds
return function cleanup() {
//When we leave component we stop the timer
clearInterval(timerID);
};
}, [state.test]); // <- here add dependency
Reason
Your effect function is called only once when component is mounted and it stored timer functions reference. now when you state changes your timer function is also updated outside but not inside of useEffect.
useEffect still uses old reference when state was 1 so inside it State always going to be 1 for that referred timer function
Now when you pass state.test as dependency. when state get changed your effect will updated and it now start using new timer function which has new state.
So now, you can have updated state in your timer function. and your condition can evaluate correctly.
if any doubts please comment.
You are not doing anything wrong, your useEffect() has a completely different value in memory and without knowing this behavior about useEffect() you have nothing in there telling useEffect() to stop looking at that old value and start looking at the new value. As Hardik wrote, your useEffect() is called only once, so you still have that old value that was originally called in there and useEffect has no idea that your timer has changed since. It will be referencing that original value forever.
What you can do is completely remove the empty array as the second argument and you will notice the difference in behavior.
Using a direct reference to the variable you are using in your state as suggested by Hardik seems to be the way to go.
So again, useEffect() is not being called a second time and as a result, nothing inside it is being ran again so it all in stale reference.
One of the tips the facebook team gives to mitigate this bug:
When you have a useEffect function that references a state, props, or context values, add them to your dependency list. In other words, if you have a props called trackId, you would want to do something like this:
useEffect(() => {
trackId
}, [trackId]);
I see a couple of potential issues, first of all you need to be calling this.state and this.setState. I'd guess state is undefined but this.state won't be. You also don't need to spread your state in your setState function, this.setState({ test: 3}); is good enough, the setState function does this for you.
Secondly you need to update state for every change, it looks like you're only updating if the test value is 3, I'm surprised it's ever 3 with this implementation

When exactly do React Hook states get set?

I am trying to understand the order of execution for the following piece of code involving React Hook states:
const App = () => {
const [ searchedCountry, setSearchedCountry ] = useState('');
const [ filteredArr, setFilteredArr ] = useState([]);
const [ filteredLength, setFilteredLength ] = useState(0);
//...
const filterCountries = (event) => {
event.preventDefault();
setFilteredArr(countries.filter(country => country.name.includes(searchedCountry)));
setFilteredLength(filteredArr.length);
console.log("filtered arr length", filtered.length);
}
//...
}
When filterCountries is triggered, setFilteredArr sets the state, filteredArr, to an array filtered by a query. However, when exactly does filteredArr get set?
filteredArr.length returns 0, meaning filteredArr has not been set yet, even after calling setFilteredArr.
At first, I thought by executing setFilteredArr, the component re-renders, causing execution to skip the method calls after setFilteredArr. That would explain why filteredArr.length is 0. However, the console.log is still called, meaning after a component re-renders, the order of execution is actually resumed.
What is going on? When exactly does filteredArr get set?
The thing to remember is: all your state variables are local variables. They only exist for this particular time the component rendered. So on the first render, console.log("filtered arr length", filteredArr.length); is referring to the array that exists on that first render. filteredArr will never be assigned a new array (it can't, it's a const), and unless you mutate the array (which you shouldn't), the length of that array will always be 0.
When you call setFilteredArr, this instructs react to rerender the component. React might do the rendering synchronously, or it might wait to try to batch up changes. When that 2nd render happens, you make a call to useState and get back the new value. This is assigned to a local variable named filteredArr, but this is a completely different variable than the one we had on the first render. The console.log statement in that first render, will have no way to access the variable in the second render. But the second render has access to it, so any logging you do the second time will show the second array.
setState or 'setFilteredLength' is an async operation. After you call it, it will take some time to update the state (as it is async, it will not wait for that update. It will simply execute the next line) so when it executes the console.log the value has not changed -> yet

Correct/Incorrect to ignore some React useEffect dependency warnings?

Here's some sample code I've written that works fine:
useEffect(() => {
if (!rolesIsLoading && rolesStatus === 200) {
customer.roles = rolesData.roles;
}
}, [rolesIsLoading, rolesStatus]);
I'm getting this warning in the console:
React Hook useEffect has missing dependencies: 'customer.roles' and 'rolesData.roles'. Either include them or remove the dependency array react-hooks/exhaustive-deps
The thing is, the code works fine now and, in some cases, when I add such dependencies, as instructed, I end up with an endless loop or some other problem.
I'd appreciate any advice you might have as to how you resolved this when you've encountered a similar situation.
From React DOCS:
Conditionally firing an effect
The default behavior for effects is to fire the effect after every completed render. That way an effect is always recreated if one of its dependencies changes.
However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source props has changed.
To implement this, pass a second argument to useEffect that is the array of values that the effect depends on. Our updated example now looks like this:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
So, from the exercpt above we see that the dependencies array serves the purpose to conditionally fire an effect.
That warning you're getting is because you're using some external (from the effect's perspective) variables in your effect function that you're not mentioning in the dependency array.
React is worried that you might be getting new values for those variables in future renders, and since your effect uses them, React "default intent" is to re-run that effect with the new variables values. So your effect would always be up to date.
So you need to think if you want to re-run your effect if/when those variables change in the future.
Basic recommendations:
Add them to the dependency array
If they never change, there will be no difference
If they change, add some conditionals to your effect to decide whether you should do something based on the new variables values or not
If they're changing only in reference, but not in value (like functions, arrays, objects, etc), you can use the useCallback or useRef hook to preserve the "reference" for those variables from the first render and not get new references on every render.

useEffect pushing value to array only once ReactJs

So basically i started learning ReactJs and am trying to build a chatting app
now here there is section of adding Channels so here is my code
const [Channels, setChannelState] = React.useState([])
let loadedChannels = []
React.useEffect(() => {
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
console.log(loadedChannels)
setChannelState(loadedChannels)
})
},[])
so here when i tried to log the loadedChannels Array am getting all the result but when i try to set this array to my state then am getting only one result
how can i resolve this?
You have an empty array [] as a second parameter which runs the function passed to useEffect only once.
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect which you have done.
If we pass [] as second parameter this will apply effect only once.
We can pass value inside array, only on change which it will trigger useEffect call.
As per the documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
try using something like this:
const [Channels, setChannelState] = React.useState([])
React.useEffect(() => {
let loadedChannels = []
setCurrentUser(props.currentUser)
ref.ref('channels').on('child_added', snap =>{
loadedChannels.push(snap.val())
})
setChannelState([...loadedChannels])
},[])
Hope this helps, Happy coding!!!
The second argument for useEffect is called a dependency array. And when it's empty, it acts like componentDidMount() i.e it runs only once, after the first render.
You also need to be careful about setting state in useEffect, since this might create an infinite loop of re-renders if not done correctly - setCurrentUser. That is not enough code to help any further than that.
This is the perfect time to get into React. Have fun!
Second parameter is passed for conditional rendering. To understand that there are few scenarios which tell us the possible ways to use that.
There are multiple scenarios which will make it easier for you to understand the importance and functionality of the second parameter with the help of the example from demo.
Scenario 1
Second parameter as empty array,
useEffect(() => {
console.log(`You clicked ${count} times`);
},
[ ]) //called only once similar to componentDidMount
If we pass an empty array, it means that we are not interested in monitoring any of the values so that the useEffect won’t be called except on mounting and before un-mounting. This mimic’s the working of componentDidMount and componentWillUnmount, it will only run once.
Scenario 2
Second parameter with value(s),
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]); //function inside useEffect will be called when "count" will be updated
The value passed as the second parameter (array) here in our example: count will be responsible for the execution of the function inside the useEffect Hook. If the value inside the array will be updated, then and only then the function will be executed.
What if we have multiple useEffect hooks and we need to update just few of them? Conditional rendering comes in light. Conditional rendering is achieved by passing the second parameter instead of just empty array. The values in the array will be monitored and the useEffect function will only be called when the monitored value(s) is/are updated.
Refer these links for more info
https://stackblitz.com/edit/react-hooks-intro
https://www.c-sharpcorner.com/article/react-hooks-introduction/

Resources