useState is executed even dependency doesn't change - reactjs

I have to fetch an API when a state changes, i've used useEffect adding the dependency like this
const [ activeLottery, setActiveLottery ] = useState();
useEffect(() => {
console.log('fetching')
}, [activeLottery])
return (
<div className="container">
<Carousel
lotteries={ lotteries }
isLoading={ isLoading }
slideClick={ setActiveLottery }
/>
</div>
);
The Component Carousel has an onClick function which changes activeLottery and it works properly since the state is mutated. But it executes the fetch when the component is rendered. Shouldn't do it when activeLottery changes?
Any hint would be much appreciated.
thanks

By default, useEffect always runs after the first render.
The dependency array in useEffect lets you specify the conditions to trigger it. If you provide useEffect an empty dependency array, it’ll run exactly once.
If you want to make API call when activeLottery changes then write condition inside useEffect.
useEffect(() => {
if(activeLottery?.length){ // if activeLottery is array then you can check it's length
// Your API call goes here
}
console.log('fetching')
}, [activeLottery])
Additionally, you can check out this awesome StackOverflow answer: https://stackoverflow.com/a/59841947/16420018
And this article by Dan Abramov: https://overreacted.io/a-complete-guide-to-useeffect/

Related

React Useeffect running when page loads

Am using useEffect in a react functional component to fetch data from an external API but it keeps calling the API endpoint on render on the page .
Am looking for a way to stop the useeffect from running on render on the component
Use the dependency array (second argument to the useEffect), so you can specify when you need to run the useEffect.
The problem here is that you have not used the dependency array, so that it executes every time. By adding a dependency array, you specify the changes where you want useEffect to run.
useEffect(()=>{
},[<dependency array: which contains the properties>]);
If you leave the dependency array empty, it will run only once. Therefore if you want the API call to run only once, add an empty array as the second argument to your useEffect. This is your solution.
Like this:
useEffect(()=>{
//Your API Call
},[]);
useEffect is always meant to run after all the changes or render effects are update in the DOM. It will not run while or before the DOM is updated. You may not have given the second argument to useEffect, which if u do not provide will cause the useEffect to execute on each and every change. Assuming you only want to call the API just once when on after the first render you should provide an empty array.
Runs on all updates, see no second argument to useEffect:
useEffect(() => { /* call API */ });
Runs when the prop or state changes, see the second argument:
useEffect(() => { /* call API */ }, [prop, state]);
Runs only once, see the empty second argument:
useEffect(() => { /* call API */ }, []);
I recommend you to read the full documentation about the React useEffect hook.
Here is a easy example of using useEffect
function functionalComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const url = 'https://randomuser.me/api/?results=10';
fetch(url)
.then(data => {
setData(data);
})
.catch(error => console.error(error))
}, []); // it's necessary to use [] to avoid the re-rendering
return <React.Fragment>
{data !== null && (
<React.Fragment>
{data.results.map(data => (
<div>
{data.gender}
</div>
))}
</React.Fragment>
)}
</React.Fragment>;
}
Maybe in your useEffect implementation you are avoiding the [] dependencies, this is a bit hard to understand if you come from class states. This on hooks review when a state element inside the hook change, for example if you are using an element that always change like a prop that you pass throught another component you might be setting inside the dependencies or another state, if you do not need any dependency just use it empty like the example above. As you can see in the documentation sometimes the dependencies are not used, this might generate an infinite loop.

What should be the dependencies of useEffect Hook?

As it's said, the useEffect hook is the place where we do the side-effects related part. I'm having a confusion of what dependencies should be passed in the dependency array of useEffect hook?
The React documentation says
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.
Consider an example:
export default function App() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("component mounted");
}, []);
const update = () => {
for(let i=0; i<5;i++){
setCount(count+i)
}
};
return (
<div>
{console.log(count)}
{count}
<button type="button" onClick={update}>
Add
</button>
</div>
);
}
Going with the above statement, we should pass the count variable as a dependency to useEffect, it will make the useEffect re run.
But in the above example, not passing the count dependency doesn't create any problem with the UI. There is not stale value. The variable updates as expected, UI re-renders with exact value, logs the current value.
So my question is, why should we pass the count variable as dependency to useEffect. The UI is working correctly?
UPDATE
I know the useEffect callback is triggered everytime when the value in dependency array changes. My question is what should go in to the dependency array? Do we really need to pass state variables?
Thanks in advance.
To demonstrate and understand the issue print the count variable inside
React.useEffect(() => {
console.log("component updated ", count);
}, []); // try to add it here
click again the button and see how it logs to the console
EDIT:
If you want to get notified by your IDE that you are missing dependencies then use this plugin
https://reactjs.org/docs/hooks-rules.html#eslint-plugin
Keep in mind that it will still complain if you want to simulate onMount
What should go into the dependency array?
Those things (props/state) that change over time and that are used by the effect.
In the example, the UI works correctly because setState re-renders the component.
But if we do some side-effect like calling an alert on change of count, we have to pass count to the dependency array. This will make sure the callback is called everytime the dependency (count) in our case, changes.
React.useEffect(() => {
alert(`Count ${count}`); // call everytime count changes
}, [count]); // makes the callback run everytime, the count updates.
If property from second argument in useEffect change - then component will rerender.
If you pass the count -> then component rerender after count change - one time.
React.useEffect(() => {
console.log("component mounted");
}, [count]);
Quick answer:
Pass everytime you need to trigger refreshing component.
useEffect will get called in an infinite loop unless you give it dependencies.
React.useEffect(() => {
console.log("Looped endlessly");
}); // dependencies parameter missing
Adding an empty dependency list will cause it to get called just once on component did mount
React.useEffect(() => {
console.log("Called once on component mount");
}, []); // empty dependency list
Add a state to the dependency list to get called when state gets updated
React.useEffect(() => {
console.log("Called once on component mount and whenever count changes");
console.log("Count: " + count);
}, [count]); // count as a dependency

How do hooks know when to trigger (or not trigger) the body of useEffect?

Looking at the official react docs, an example is given for writing a custom hook, as below:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function ChatRecipientPicker() {
const [recipientID, setRecipientID] = useState(1);
const isRecipientOnline = useFriendStatus(recipientID);
return (
<>
<Circle color={isRecipientOnline ? 'green' : 'red'} />
<select
value={recipientID}
onChange={e => setRecipientID(Number(e.target.value))}
>
{friendList.map(friend => (
<option key={friend.id} value={friend.id}>
{friend.name}
</option>
))}
</select>
</>
);
}
The part I'm confused about - my understanding is useEffect(...) will trigger each time the component re-renders, which as currently described, would only happen when setRecipientID is called. But, say another state variable is added, say, const [nameFilter, setNameFilter] = useState(''). In this case, the component will re-render every time a user types into the filter, which I think will trigger the "connection" logic in useEffect.
I think this would work if useEffect took in the friendID as the 2nd param, but being new to react I don't want to assume the official docs are not written in a resilient way, which means I'm wrong and the react plumbing somehow knows to not connect each re-render - but how?
To useEffect you need to provide a callback that is going to be executed and, optionally, an array of values that will determine when the effect is triggered. Here you have three options:
You pass nothing - useEffect is triggered each time components is rendered
You pass en empty array - useEffect is triggered only once, when component is mounted
You pass an array with props and state variables - useEffect is triggered when the component is first mounted and each time at least one of the variables changes
React does not do anything else to reduce the number of times useEffect is called, so yes you do need to explicitly provide variables your effect depends on (friendID in your case)

React - issue with useEffect() and axios.get

I am struggling to understand why my code is not working. I am using the useEffect() hook to make a call to an API, and then I am using setState() to update my component state. In my JSX, I am mapping my info array to render the data.
Here's my code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
function App() {
const [info, setInfo] = useState();
console.log(info);
useEffect(() => {
const getUsers = async () => {
const res = await axios('https://api.mocki.io/v1/b043df5a');
console.log(res.data);
setInfo(res.data);
};
getUsers();
}, []);
return (
<div>
<input type='text' placeholder='Search users' />
<input type='text' placeholder='Search users' />
{info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
</div>
);
}
export default App;
However, I get this error: 'TypeError: Cannot read property 'map' of undefined'. My best guess is that my JSX is rendered before my state is populated with API info.
The code does work when I write the following JSX, which allows me to check if 'info' is true:
{info && info.map((el, index) => {
console.log(el);
return <h1 key={index}>{el.city}</h1>;
})}
Is this a normal behavior? Why is useEffect not populating my state before my page is rendered?
I would appreciate your help, as I am struggling to find the solution to this specific issue.
Thanks!
Just do this:
const [info, setInfo] = useState([]);
The issue is that you have no intial value and therefore it automatically defaults to undefined. Now you are trying to call .map on a value that is undefined therefore it throws an error. With an empty array as the initial value however, .map will loop over an empty array on the first render (before the useEffect) and it won't throw any error.
that's because useEffect hook will run after the dom render phase finished and one more thing that can cause the delay of getting data is the fact that you're calling an asynchronous function which usually get some time to finished.
so what are the possible options here:
just use empty array [] as default value
check the length of the state like info.length && whatever...
sometimes you can use the useLayoutEffect which is kinda a synchronous operation. but in your case which is an api calls solution 1 and 2 is the answer
You are trying to iterate over undefined it's normal cause you need to check first before data will come to your state. You can achieve this in two ways:
info && info.map
But your initial state must be falsy value like:
useState(null)
Or better way to set initial state to empty array and map will run when data will come and you will get no error.
So basicly useEffect function is equivalent of componentDidMount. What I'm trying to say your component renders and then it executes function passed in useEffect. To avoid this eighter use check that you introduced as a fix yourself or pass default value to useState method. I would suggest your option but with condition if info exists show it and if it's not then show some kind of loading indicator.
A use Effect with a [] as second can be interpreted as a 'componentDidMount'
To give a very simple answer, your code is 'executed' the first time without the useEffect. So indeed, 'info' will not exist. At this point your 'info' variable is not yet defined. Then, when your component 'is mounted', it will execute the code in your useEffect. Only then info will be filled in.
I would recommend to go through this documentation to fully understand this: https://reactjs.org/docs/state-and-lifecycle.html

React hooks update sort of whenever they feel like it

So, I'm using hooks to manage the state of a set of forms, set up like so:
const [fieldValues, setFieldValues] = useState({}) // Nothing, at first
When setting the value, the state doesn't update:
const handleSetValues = values => {
const _fieldValues = {
...fieldValues,
...values
}
setFieldValues(_fieldValues)
console.log(fieldValues) // these won't be updated
setTimeout(() => {
console.log(fieldValues) // after ten seconds, it's still not updated
},10000)
}
If I call the function a second time, it'll have updated, but that's not gonna work for me.
I never saw behaviour like this in class components.
Is it meant to... like, not update? Or just update whenever it feels like it? Really confusing behaviour.
setFieldValues(_fieldValues) is an async call, means you won't able to get the result in the very next line of this.
You can use useEffect hook.
useEffect(() => {
// do your work here
}, [fieldValues]);
It seems from your question that you have background of Class components of React, so useEffect is similar to componentDidMount and componentDidUpdate lifecycle methods.
useEffect calls whenever the state in the dependency array (in your case [fieldValues]) changes and you get the updated value in useEffect body.
You can also perform componentWillUnmount work in useEffect as well.
Have a brief guide.
setFieldValues is an asynchronous function, so logging the value below the statement will not have any effect.
Regarding using setTimeout, the function would capture the current value of props being passed to it and hence that would be the value printed to the console. This is true to any JS function, see the snippet below:
function init(val) {
setTimeout(() => {
console.log(val);
}, 1000);
}
let counterVal = 1;
init(counterVal);
counterVal++;
So how can we print the values when the value changes? The easy mechanism is to use a useEffect:
useEffect(() => {
console.log(fieldValues)
}, [fieldValues]);

Resources