I have a useEffect where currently get from the redux the data and also clean up it during the unmounting:
useEffect(
dispatch(getData...)
() => {
dispatch(cleanData...)
},
[url]
);
When I set the url as dependency for using as componentDidupdate and updating the component if url is changed it throws some warnings in the console about not being able to work with unmounted component while functionality seems to work. What is the ideal way to have these 3 lifecycle methods in the same place for the useEffect?
useEffect expects two arguments. Callback Function that will get called at the initial render and when the dependencies gets changed. Second argument is dependency array.
So you can call dispatch function on initial render and on change of url . You have to return a function that is a cleanup function.
useEffect(() => {
dispatch(getData)
return () => {
dispatch(cleanData)
}
},[url]);
Related
Here's my usecase for which I would really want useEffect be called within a function but I'm unable to do so.
I have a dropdown which gets it's data from an API call that I call at the start of the page load. Based on the selection from this dropdown, I do a setState for the ID of the selected value taken from the dropdown. I then have another dropdown which is part of a library called React QueryBuilder for which I need to pass an onChange function. Within this onChange function, I call useEffect and then pass the ID that I got from the previously mentioned setState - When I do this, I get an error message telling me I cannot call useEffect within a function but I need it to be called here because I need to make an API call and pass the ID to the API.
If i have the function name in all small cases, I get the below error:
React Hook "useEffect" is called in function "functionname" which is neither a React function component or a custom React Hook
If i rename the function as "FunctionName", I get the below error:
*
Invalid hook call. Hooks can only be called inside the body of a
function component
If i don't use useEffect, that API call goes into a continuous loop!
I'm unable to think of any other way - Can someone shed some light or help me in the right direction on what to do here ?
Update:
For API Calls, I am currently using "useEffect" and "axios" - is that wrong? I add the "useEffect" if there's a continuos loop problem. See below example:
const ABCFunction = (event) => {
axios.get("APIURL/Getsomething").then((response) => { setState(response.data);
});
In one part of the code, the above did not go into a continuous loop, but in another part where I had called my API the same way, it went into a continuos loop, so I added useEffect which gave me my issue.
Here's the API call which I did with the useEffect hook - this Function is located inside the page.
import abc
import cdf
..
..
const pageView =() => {
.
.
.
.
const getFunction = (fieldName) => {
const field = filteredIfData.find(fld => fld.name === fieldName);
useEffect(() => {
axios.get(`$apiURL`).then((response) => {
setState(response.data);
},[]);
.
.
.
};
export default pageView
From your description and error message, I got two key things what you did wrong:
Calling useEffect hook from onChange handler:
onChange={FunctionThatCallsUseEFfectHook}
Without useEffect hook, getting infinite loop through immediate call
onChange={FunctionThatCallsImmediately()}
Solutions:
You can't use the useEffect hook from event handlers. Rather, use the useEffect hook based the value change:
useEffect(() => { // not inside onChange handler
//...
}, [ValueThatChangesOnSelection])
Without useEffect hook, call the function only on change but not immediately:
onChange={() => onChangeHandler()}
Or,
onChange={onChangeHandler}
But not this:
onChange={onChangeHandler()}
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.
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
This one below doesn't work:
//Uncaught TypeError: Cannot read property 'name' of undefined
console.log(countries[1].name)
This one below does work:
<ul>
{countries.map(country => (
<li>{country.name}</li>
))}
</ul>
Any ide why one method of printing name does work, while the other doesn't?
Coz you can loop through the empty array, but you can't access the index which is not available yet
// So if
countries = []
// this will not throw error
{countries.map(country => (
<li>{country.name}</li>
))}
// but this will
console.log(countries[1].name)
// if you want to check try to run this
console.log(countries.length ? countries[1].name : "not available yer");
The usage of useEffect hook notifies React that component has to perform some side-effects(passed as a callback function to the hook) after it has been rendered, The default behavior of useEffect will run both after the first render and after every update, but when an empty array is passed as a dependency the side-effect will be performed only once after the component has been mounted for the first time.
In the case above useEffect(hook, []) the callback hook will be called after the component has mounted for the first time, which means the component will render with the initial state on it's first render which is an empty array ([]).
That is why when you try to access countries[1].name it errors out, because the value of countries is still an empty array on the first render.
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
setCountries(response.data)
})
}
useEffect(hook, [])
// can not use index expression to get the first element because
// the value of countries is still an empty array on first render
// it only gets populated when axios.get call is succesful inside the
// callback in useEffect hook after the component has mounted for the first time
console.log(countries[1].name)
Solution
Check for the length of the array before trying to get the first element,
if (countries.length) {
console.log(countries[1].name)
}
P.S.- You should be using a .catch block for handling the error when the API call fails.
There is an example solution for a type of request like this in the React document:
https://reactjs.org/docs/hooks-effect.html
The hooks provided by React are for the most part, asynchronous functions provided by React, to help manage the loading of data, presenting it to the DOM, and dealing with updates. The useEffect behaves in a similar way to componentHasLoaded, where the hook is triggered once the functional component has rendered, and the DOM has been loaded, but it may not have been presented to the user yet. It's important to remember this when working with useEffect. useState is another asynchronous hook, but it provides access to the state property of the hook after it has been instantiated, and won't immediately trigger a re-render of the component, unless the data is updated.
The reason you get an undefined error when you attempt to access console.log(countries[1].name) is because the array at that point is still empty.
I'll explain in code:
const myComponent = () => {
// initialise countries: []
const [ countries, setCountries ] = useState([])
const hook = () => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
// This is allow you to see the DOM change after the effect has run
setTimeout(() => setCountries(response.data), 5000);
})
}
// Tell react to run useEffect once the component is loaded
useEffect(hook, [])
// Display data
return (
<p>Countries: {countries.length}<p>
);
};
Because useEffect is an asynchronous function, it doesn't block the execution of the function and the rendering of the DOM, but refreshes the DOM once useEffect is completed. In this case, you are setting the country list, based on the result of the useEffect function.
The useEffect function will still trigger, you will have access to the state, and the function will re-render when the state is updated.
See codepen example:
https://codepen.io/jmitchell38488/pen/OJMXZPv
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]);
My component has two Rect.useEffect hook
const Example = ({ user }) => {
React.useEffect(() => {
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};
I update this component using mobx. It is re-rendered correctly. But "Why not me?" is printed only once.
As per official docs
By default, effects run after every completed render
This means console.log("Why not me?"); too should run every time prop user is updated. But it doesn't. The console output is this
What's the reason behind this apparent inconsistency?
My complete code can be viewed here
In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react lifecycle.
This behaviour happens because you have user count as a observable variable.
According to the mobx-react docs
Observer is a React component, which applies observer to an anonymous
region in your component. It takes as children a single, argumentless
function which should return exactly one React component. The
rendering in the function will be tracked and automatically
re-rendered when needed.
and mobx docs
When autorun is used, the provided function will always be triggered
once immediately and then again each time one of its dependencies
changes.
You can confirm this behvaior by logging directly inside the functional component and you will observer that the component is only rendered once
EDIT:
To answer your question
If I change useEffect to this
React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));
basically remove anonymous function from useEffect and just pass
autorun directly, then it is run only once. Why is it so? What's the
difference?
The difference is that autorun returns a disposer function which when run will dispose of the autorun and would not longer execute it.
From the docs:
The return value from autorun is a disposer function, which can be
used to dispose of the autorun when you no longer need it.
Now what happens is that since useEffect executes the callback provided to it when it runs, the callback executed is the disposer function returned by autorun which essentially cancels your autorun.
It looks like your component doesn't rerender. autorun receives a callback and might call it independently from render.
Example component rerenders only when its parent rerenders or when its props change.
Use this code to observe what's really happening:
const Example = ({ user }) => {
console.log('render');
React.useEffect(() => {
console.log('useEffect autorun');
autorun(() => {
console.log("inside autorun", user.count);
});
});
// Only runs once
React.useEffect(() => {
console.log("Why not me?");
});
return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};