API is getting called inside useEffet multiple times (React js) - reactjs

I am calling an function inside useEffect but it is rendering multiple times.
let courseVideo = async () => {
const response = await api
.post(COURSE_VDO_URL, JSON.stringify({ courseID, username }), {
headers: { "Content-Type": "application/json" },
"Access-Control-Allow-Credentials": true,
})
.then((data) => {
setCoursesVdoList(data.data.data.lessons);
setCompletedEpisode(data.data.data.lessonsCompleted)
setExistingCourseID(data.data.data.courseID)
});
};
useEffect(() => {
courseVideo();
const interval = setInterval(() => {
setCount(!count)
}, 300000)
}, [count, completedEpisode]);
the count is used because I want to force rerender the component after 5 minutes.

You can try using functional updater and clearing the timer
useEffect(() => {
let ignore = false;
// depends on courseID, username
// need to include them in depedency
courseVideo();
const interval = setInterval(() => {
if (ignore) {
// skip setting state on unmounted component
return;
}
// use functional updater to remove dependency on count
// as this will stop infinite render cycles
setCount(c => !c)
// without clearing this could trigger multiple times
}, 300000)
return () => {
ignore = true;
clearInterval(interval)
}
}, [completedEpisode]); //include courseID, username dependencies, they are used in courseVideo
You can read more about useEffect life cycle in the new beta docs
Hope it helps

The courseVideo function is being called every time the component re-renders. It is being passed as a dependency to the useEffect hook. That why, it's multiples time.
You need to only call it when the component is initially rendered. You can clean up interval when the component unmounts.
useEffect(() => {
courseVideo();
const interval = setInterval(() => {
setCount((prevCount) => !prevCount);
}, 300000);
return () => clearInterval(interval);
}, []);

you can write another useEffect which one render only initial rendered time.
and if the courseID and username change dynamically then should be added this two variable in dependancy list otherwise leave it empty.
useEffect(()=> {
courseVideo()
},[courseID, username])

Related

Refactoring useEffect to only require one database call

At the moment, I have a component which completes some backend calls to decide when to start displaying the UI.
It's structured like this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
The purpose here, is that verifyUser() is supposed to run first, and in the response to verifyUser(), a user id is provided by the backend.
const verifyUser = async () => {
if (!localStorage.getItem('auth')) {
return
}
if (localStorage.getItem('auth')) {
await axios.post("/api/checkAuth", {
token: JSON.parse(localStorage.getItem('auth'))
})
.then((response) => {
return setUserId(response.data.user_id)
})
.catch((err) => {
console.log(err)
localStorage.removeItem('auth')
})
}
}
As a result of this, the fetchData() function is supposed to wait until the verifyUser() function has stopped resolving, so it can use the user id in the database query.
However, at the moment it...
Calls once, without the user id
Then calls again, with the user id (and therefore resolves successfully)
Here's the function for reference:
const fetchData = async () => {
console.log("Fetch data called.")
console.log(userId)
await axios.post("/api/fetch/fetchDetails", {
user_id: userId
})
.then((response) => {
// Sets user details in here...
return response
})
.then(() => {
return setFetching(false)
})
.catch((err) => {
console.log(err)
})
}
What I'm trying to achieve here is to essentially remove any concurrency and just run the functions sequentially. I'm not 100% sure what the best practice here would be, so some feedback would be appreciated!
Your useEffect is missing a dependency array argument:
useEffect(() => {
getData()
})
should be:
useEffect(() => {
getData()
}, [])
Without that argument, useEffect will run once each time your component renders. With that argument, it will only run once, when the component is first mounted (ie. after the first render).
If you needed it to depend on another variable (eg. user.id isn't defined on load, but is later on) you could put that variable in the dependency array, ie.
useEffect(() => {
if (!user.id) return;
getData()
}, [user.id])
This version would run once when the component is mounted, then again if the user.id changes (eg. if it goes from null to an actual number).
In React, the useEffect hook accepts two arguments - the first one is a function (this is the "effect"), and the second one is a dependency array. The simplest useEffect hook looks like this:
useEffect(() => {
}, [])
The above hook has no dependency (because the array is empty), and runs only when the component initially mounts, and then goes silent.
If you don't pass in a dependency array as the second argument, as #machineghost said, the hook will run the "effect" function every time your component re-renders.
Now to your specific problem. You want to run fetchData after verifyUser has resolved its Promise, so you'd add the outcome of verifyUser as a dependency to a separate useEffect hook that calls fetchData. In this case, the outcome is setting userId.
So instead of this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
Do this:
useEffect(() => {
verifyUser();
}, []);
useEffect(() => {
if (userId) { // assuming userId has a false-y value before verifyUser resolved
await fetchData();
}
}, [userId])

Add object array to setState

I'm trying to add an object array in the state series. With this code the useEffect function get stuck in an infinite loop. How can I solve this? Without adding the series const as parameter I get the error about a missing dependency and the code will only run on startup.
import React, { useState, useEffect } from "react";
const LineChart = () => {
const [series, setSeries] = useState([]);
useEffect(() => {
const url = "http://localhost:4000";
const fetchData = async () => {
try {
fetch(url, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
let chartData = data.testRunSummaries
.map(function (testrun) {
return {
duration: testrun.endTime - testrun.startTime,
label: testrun.testSetName + "#" + testrun.userFacingId,
testrun: testrun.testRunId,
status: testrun.status,
};
});
setSeries(chartData, ...series);
console.log(series);
});
} catch (error) {
console.log(error);
}
};
fetchData();
}, [series]);
return (
...
);
};
export default LineChart;
series is in your useEffect dependency array. And your useEffect is changing series. So obviously you'll be stuck in a infinite loop.
You don't need your series to be set as a dependency for useEffect.
As your useEffect will only be trigger once on mount, you can just have
setSeries(chartData);
And if you really need to have former values of series, you should use
setSeries(series => [...chartData, ...series]);
Moreover, seeing your
setSeries(chartData, ...series);
console.log(series);
Let me remind you that setState is async there is no way this will log your updated state :)

useEffect: Can't perform a React state update on an unmounted component [duplicate]

When fetching data I'm getting: Can't perform a React state update on an unmounted component. The app still works, but react is suggesting I might be causing a memory leak.
This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function."
Why do I keep getting this warning?
I tried researching these solutions:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
but this still was giving me the warning.
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.log(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
Edit:
In my api file I added an AbortController() and used a signal so I can cancel a request.
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
My spotify.getArtistProfile() looks like this
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
but because my signal is used for individual api calls that are resolved in a Promise.all I can't abort() that promise so I will always be setting the state.
For me, clean the state in the unmount of the component helped.
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
Sharing the AbortController between the fetch() requests is the right approach.
When any of the Promises are aborted, Promise.all() will reject with AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>
For example, you have some component that does some asynchronous actions, then writes the result to state and displays the state content on a page:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Let's say that user clicks some link when doVeryLongRequest() still executes. MyComponent is unmounted but the request is still alive and when it gets a response it tries to set state in lines (1) and (2) and tries to change the appropriate nodes in HTML. We'll get an error from subject.
We can fix it by checking whether compponent is still mounted or not. Let's create a componentMounted ref (line (3) below) and set it true. When component is unmounted we'll set it to false (line (4) below). And let's check the componentMounted variable every time we try to set state (line (5) below).
The code with fixes:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
const componentMounted = useRef(true); // (3) component is mounted
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
if (componentMounted.current){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted.current = false; // (4) set it to false when we leave the page
}
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
Why do I keep getting this warning?
The intention of this warning is to help you prevent memory leaks in your application. If the component updates it's state after it has been unmounted from the DOM, this is an indication that there could be a memory leak, but it is an indication with a lot of false positives.
How do I know if I have a memory leak?
You have a memory leak if an object that lives longer than your component holds a reference to it, either directly or indirectly. This usually happens when you subscribe to events or changes of some kind without unsubscribing when your component unmounts from the DOM.
It typically looks like this:
useEffect(() => {
function handleChange() {
setState(store.getState())
}
// "store" lives longer than the component,
// and will hold a reference to the handleChange function.
// Preventing the component to be garbage collected after
// unmount.
store.subscribe(handleChange)
// Uncomment the line below to avoid memory leak in your component
// return () => store.unsubscribe(handleChange)
}, [])
Where store is an object that lives further up the React tree (possibly in a context provider), or in global/module scope. Another example is subscribing to events:
useEffect(() => {
function handleScroll() {
setState(window.scrollY)
}
// document is an object in global scope, and will hold a reference
// to the handleScroll function, preventing garbage collection
document.addEventListener('scroll', handleScroll)
// Uncomment the line below to avoid memory leak in your component
// return () => document.removeEventListener(handleScroll)
}, [])
Another example worth remembering is the web API setInterval, which can also cause memory leak if you forget to call clearInterval when unmounting.
But that is not what I am doing, why should I care about this warning?
React's strategy to warn whenever state updates happen after your component has unmounted creates a lot of false positives. The most common I've seen is by setting state after an asynchronous network request:
async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}
You could technically argue that this also is a memory leak, since the component isn't released immediately after it is no longer needed. If your "post" takes a long time to complete, then it will take a long time to for the memory to be released. However, this is not something you should worry about, because it will be garbage collected eventually. In these cases, you could simply ignore the warning.
But it is so annoying to see the warning, how do I remove it?
There are a lot of blogs and answers on stackoverflow suggesting to keep track of the mounted state of your component and wrap your state updates in an if-statement:
let isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!isMountedRef.current) {
setPending(false)
}
}
This is not an recommended approach! Not only does it make the code less readable and adds runtime overhead, but it might also might not work well with future features of React. It also does nothing at all about the "memory leak", the component will still live just as long as without that extra code.
The recommended way to deal with this is to either cancel the asynchronous function (with for instance the AbortController API), or to ignore it.
In fact, React dev team recognises the fact that avoiding false positives is too difficult, and has removed the warning in v18 of React.
You can try this set a state like this and check if your component mounted or not. This way you are sure that if your component is unmounted you are not trying to fetch something.
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if(!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
Hope this will help you.
I had a similar issue with a scroll to top and #CalosVallejo answer solved it :) Thank you so much!!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className="back-to-top">
<h1
className="scrollTop"
onClick={scrollTop}
style={{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};
I have getting same warning, This solution Worked for me ->
useEffect(() => {
const unsubscribe = fetchData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
if you have more then one fetch function then
const getData = () => {
fetch1();
fetch2();
fetch3();
}
useEffect(() => {
const unsubscribe = getData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
This error occurs when u perform state update on current component after navigating to other component:
for example
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
dispatch(login(res.data.data)); // line#5 logging user in
setSigningIn(false); // line#6 updating some state
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
In above case on line#5 I'm dispatching login action which in return navigates user to the dashboard and hence login screen now gets unmounted.
Now when React Native reaches as line#6 and see there is state being updated, it yells out loud that how do I do this, the login component is there no more.
Solution:
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
setSigningIn(false); // line#6 updating some state -- moved this line up
dispatch(login(res.data.data)); // line#5 logging user in
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
Just move react state update above, move line 6 up the line 5.
Now state is being updated before navigating the user away. WIN WIN
there are many answers but I thought I could demonstrate more simply how the abort works (at least how it fixed the issue for me):
useEffect(() => {
// get abortion variables
let abortController = new AbortController();
let aborted = abortController.signal.aborted; // true || false
async function fetchResults() {
let response = await fetch(`[WEBSITE LINK]`);
let data = await response.json();
aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
if (aborted === false) {
// All your 'set states' inside this kind of 'if' statement
setState(data);
}
}
fetchResults();
return () => {
abortController.abort();
};
}, [])
Other Methods:
https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8
If the user navigates away, or something else causes the component to get destroyed before the async call comes back and tries to setState on it, it will cause the error. It's generally harmless if it is, indeed, a late-finish async call. There's a couple of ways to silence the error.
If you're implementing a hook like useAsync you can declare your useStates with let instead of const, and, in the destructor returned by useEffect, set the setState function(s) to a no-op function.
export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
let [parameters, setParameters] = useState(rest);
if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
setParameters(rest);
const refresh: () => void = useCallback(() => {
const promise: Promise<T | void> = gettor
.apply(null, parameters)
.then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
.catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
return promise;
}, [gettor, parameters]);
useEffect(() => {
refresh();
// and for when async finishes after user navs away //////////
return () => { setTuple = setParameters = (() => undefined) }
}, [refresh]);
let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
return tuple;
}
That won't work well in a component, though. There, you can wrap useState in a function which tracks mounted/unmounted, and wraps the returned setState function with the if-check.
export const MyComponent = () => {
const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
// ..etc.
// imported from elsewhere ////
export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
const [val, setVal] = useStateTuple;
const [isMounted, setIsMounted] = useState(true);
useEffect(() => () => setIsMounted(false), []);
return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}
You could then create a useStateAsync hook to streamline a bit.
export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
return useUnlessUnmounted(useState(initialState));
}
Try to add the dependencies in useEffect:
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [fetchData, props.spotifyAPI])
Usually this problem occurs when you showing the component conditionally, for example:
showModal && <Modal onClose={toggleModal}/>
You can try to do some little tricks in the Modal onClose function, like
setTimeout(onClose, 0)
This works for me :')
const [state, setState] = useState({});
useEffect( async ()=>{
let data= await props.data; // data from API too
setState(users);
},[props.data]);
I had this problem in React Native iOS and fixed it by moving my setState call into a catch. See below:
Bad code (caused the error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
}
setLoading(false) // this line was OUTSIDE the catch call and triggered an error!
}
Good code (no error):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
setLoading(false) // moving this line INTO the catch call resolved the error!
}
}
Similar problem with my app, I use a useEffect to fetch some data, and then update a state with that:
useEffect(() => {
const fetchUser = async() => {
const {
data: {
queryUser
},
} = await authFetch.get(`/auth/getUser?userId=${createdBy}`);
setBlogUser(queryUser);
};
fetchUser();
return () => {
setBlogUser(null);
};
}, [_id]);
This improves upon Carlos Vallejo's answer.
useEffect(() => {
let abortController = new AbortController();
// your async action is here
return () => {
abortController.abort();
}
}, []);
in the above code, I've used AbortController to unsubscribe the effect. When the a sync action is completed, then I abort the controller and unsubscribe the effect.
it work for me ....
The easy way
let fetchingFunction= async()=>{
// fetching
}
React.useEffect(() => {
fetchingFunction();
return () => {
fetchingFunction= null
}
}, [])
options={{
filterType: "checkbox"
,
textLabels: {
body: {
noMatch: isLoading ?
:
'Sorry, there is no matching data to display',
},
},
}}

Array is Updating with useEffect but Always Get the Initial Value

This question has been asked many times but none of the solutions have worked for me. I miss something and I need your help to find it out.
I also provide the whole code to give you context.
I have a TV app that shows the production of a factory.
Initially I request the server to check if statically defined lines are working right now.
let lineIds = ["3", "1", "9"]
TvApp.getInitialProps = async (ctx) => {
try {
const res = await axios.get(`http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`)
const workingLinesResponse = await res
console.log('workingLinesResponse: data: ', workingLinesResponse.data)
return { workingLines: workingLinesResponse.data }
} catch (error) {
console.log('error getInitialProps:: ', error)
return { data: null }
}
}
In the first useEffect, I keep checking every 15 seconds if the lines are still working;
Server responds with the workingLines.
In the second useEffect, I use workingLines to get production data for the currently working lines.
My problem is;
In the first useEffect, setWorkingLines(response.data) is updating workingLines but cannot be used by the second useEffect. Second useEffect always shows the initial value.
export default function TvApp(props) {
const [workingLines, setWorkingLines] = useState(props.workingLines);
//first useEffect
useEffect(() => {
const timer = setInterval(() => {
const API = `http://192.168.1.10:8090/api/tv_app_checkline/:${lineIds}`
axios.get(API)
.then(response => {
console.log('workingLines initially:: ', response.data)
setWorkingLines(response.data)
})
.catch(error => {
console.log(error.message);
});
return () => clearInterval(timer);
}, 15000);
},[])
var activeLineId = 0
//second useEffect
useEffect(() => {
const timer = setInterval(() => {
axios.get(`http://192.168.1.10:8090/api/tv_app/:${workingLines[activeLineId]}`)//workingLines is always the initial value, not updated one.
.then(response => {
//Set reponse to use in the view.
})
if (activeLineId === workingLines.length - 1) {
activeLineId = 0
} else {
activeLineId = activeLineId + 1
}
return () => clearInterval(timer);
}, 10000);
}, []); //using workingLines as a dependency is causing the multiple api requests
}
If I use workingLines as a dependency in the second useEffect, the api request is calling multiple times.
I can check the workingLines with following useEffect:
useEffect(() => {
console.log('workingLines:: ', workingLines) //this shows that, workingLines is successfully updated by the first useEffect
}, [workingLines])
How can I use the updated workingLines in the second useEffect without calling the api multiple times?

React Hook setting state in useEffect with state in dependency array

I have a question about the correct usage with useEffect and setState in React Hooks.
here is my react app that gets a persisted configuration and some other data depending on the config like this:
function App() {
const [config, setConfig] = useState(null);
// some other states
useEffect(() => {
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}, [config]);
return (
<div >
...
</div>
);
}
If I run this App useEffect is instantly called twice one time for first render and then a second time because setConfig(data.config); is called in the axios get success function.
The user has the possibility to change his own config that is done in another request. If he does I want after state config changes that the config and some other data depending on the config is reloaded via this useEffect function.
Now since there is no setstate callback in react hooks I read somewhere I should use useEffect with the state variable in the dependency array.
How can I prevent that my useEffect is called twice right at the beginning?
I have the feeling that I am doing it all wrong.
Thank you in advance
You need to add an if condition in useEffect, because useEffect is called by default on first render.
useEffect(() => {
if(config !== null){
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}
}, [config]);
This is not right!
I guess this can call fetchData infinitely.

Resources