useEffect spamming requests - javascript

State
const [user, setUser] = useState({});
checkIfUserIsEnabled()
async function checkIfUserIsEnabled() {
const res = await fetch("http://localhost:8080/users/finduserbytoken?id=" +
getTokenIdFromURL);
res.json()
.then(res => setUser(res))
.catch(err => setErrors(err));
}
useEffect When i call my checkIfUserIsEnabled() in the useEffect below it gets rendered once and displays the false version in the return method.
useEffect(() => {
verifyEmail(getTokenIdFromURL);
checkIfUserIsEnabled();
return () => {
/* cleanup */
};
}, [/* input */])`
useEffect (2th) If i do it like this instead, it keeps spamming the requests towards my API and displays true.
useEffect(() => {
checkIfUserIsEnabled();
});
Return
return (
<div className="emailVerificationWrapper">
{user.enabled
? <h1>Thank you for registrating, {user.firstName}. Account is verified!</h1>
: <h1>Attempting to verify account...</h1>}
</div>
)
To my question(s): Why does the second useEffect spam the request? and is there a way i can make the request being rendered every ~2-3 second instead of the spam? and could i make it stop doing the request once it actually returns true?

The effect hook runs when the component mounts but also when the component updates. Because we are setting the state after every data fetch, the component updates and the effect runs again.
It fetches the data again and again. That's a bug and needs to be avoided. We only want to fetch data when the component mounts. That's why you can provide an empty array(or something which doesn't change) as second argument to the effect hook to avoid activating it on component updates(or only when that parameter changes) but only for the mounting of the component.
let URL = `http://localhost:8080/users/finduserbytoken?id=`;
async function checkIfUserIsEnabled() {
const res = await fetch(`$(URL)` +
getTokenIdFromURL);
res.json()
.then(res => {setUser(res); return Promise.resolve()})
.catch(err => {setErrors(err); return Promise.reject()});
}
useEffect(() => {
const abortController = new AbortController();
const fetchData = async() => await checkIfUserIsEnabled();
fetchData();
return () => {
abortController.abort();
};
}, [URL]);

useEffect(() => {
checkIfUserIsEnabled();
}); <-- no dependency
As your useEffect doesn't have any dependency it will run on every render, so every time you change some state and your component re-renders it will send requests.

Related

why useEffect is called infinitely

In useEffect in my react component I get data and I update a state, but I don't know why the useEffect is always executed:
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect( async()=>{
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
},[studies]);
return(
<Comp2 studies={studies}/>
)
}
Here is my second Component used in the first component...
const Comp2 = (props) => {
const [studies, setStudies]= useState([]);
React.useEffect( ()=>{
setStudies(props.studies)
},[props.studies, studies]);
return(
studies.map((study)=>{console.log(study)})
}
EDIT
const Comp2 = (props) => {
// for some brief time props.studies will be an empty array, []
// you need to decide what to do while props.studies is empty.
// you can show some loading message, show some loading status,
// show an empty list, do whatever you want to indicate
// progress, dont anxious out your users
return (
props.studies.map((study)=>{console.log(study)}
)
}
You useEffect hook depends on the updates that the state studies receive. Inside this useEffect hook you update studies. Can you see that the useEffect triggers itself?
A updates B. A runs whenever B is updated. (goes on forever)
How I'd do it?
const Comp1 = () => {
const [studies, setStudies]= useState([]);
React.useEffect(() => {
const asyncCall = async () => {
await axios.get('/api/expert/',
)
.then((response) => {
setStudies(response.data.studies);
}, (error) => {
console.log(error );
})
console.log("called+ "+ studies);
}
asyncCall();
}, []);
return(
<Comp2 studies={studies}/>
)
}
useEffect() has dependency array which causes it to execute if any value within it updates. Here, setStudies updates studies which is provided as dependency array and causes it to run again and so on. To prevent this, remove studies from the dependency array.
Refer: How the useEffect Hook Works (with Examples)

React state still undefined after fetching inside useEffect

In my react app, I am making a request to an API inside useEffect. Once the promise is returned, I am setting state. I then need to use that state to subscribe to a websocket, but state is coming back as undefined so for now I am just logging the state. I only want to do this on initial page load. How can I make an API request, save that information to state, then use that state to do something all on initial page load only?
const fetchInstruments = () => {
return axios
.post('http://localhost:5050/0/public/AssetPairs')
.then(({ data }) => {
return data.result
})
.catch((error) => console.log(error))
}
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
})
.then(()=> console.log(instruments)) //this is undefined
}, [])
}
if I were in your place, I would not use the state to subscribe to a websocket, I will use the result of the HTTP request
checkout this
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments);
return fetchedInstruments; // <======
})
.then((fetchedInstruments) => {
console.log(fetchedInstruments);
//TODO : handle websocket subscription
})
}, [])
}
You can just make your second API call when you fetched your instruments:
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
secondApiCall(fetchedInstruments); // You can do your second API call here
})
}, [])
}

useEffect infinite loop with axios

I'm stuck in an infinite loop in useEffect despite having tried the clean up function
I tried to pass [] and [usersDB] as the 2nd parameter of useEffect and it didn't work because [] makes it run only one time and it's not the behavior I'm looking for (I want it to run after each update)
const ListUsers = () => {
const [usersDB, setUsersDB] = useState([]);
useEffect(() => {
const getUsers = async () => {
const response = await axios
.get("http://localhost:4000/contacts")
.catch((error) => console.log(error.resp));
setUsersDB(response.data);
};
getUsers();
});
console.log(usersDB);
return (
<div>
<div>
{usersDB.map((user, index) => (
<CardUsers key={user._id} user={user} />
))}
</div>
</div>
);
};
export default ListUsers;
Every time the component renders, it runs your useEffect. Your useEffect calls setUsersDB, which updates state, which causes a rerender, which runs useEffect...
You can add an array of values at the end of useEffect which tells the useEffect to only run if any of the included values have changed. But if you add an empty array, it tells the useEffect to run only once, and never again, as there are no values that it depends on that have changed. You mention that you tried this, and that you want useEffect to run after each update. Each update of what? If there is some other value that is being updated elsewhere, and you want useEffect to be dependent on that value, then stick it in the dependency array.
useEffect(() => {
const getUsers = async () => {
const response = await axios
.get("http://localhost:4000/contacts")
.catch((error) => console.log(error.resp));
setUsersDB(response.data);
};
getUsers();
}, [some updating value])

"ComponentDidMount" hook triggers memory-leaks if i'm requesting data from the server

I'm slowly trying to remove "Redux" from my workflow, lets say part of components receives required data from the server, when they are be rendered on the screen (in ComponentDidMounth hook), rendering of that components controlled by html5 history router,so if the user starts jumping quickly between routes, in the console comes memory lick warning
can you please tell me some smart way how to fix it. thanks ))
also please show examples with latest react api (HOOKS)
const Posts = () => {
const [posts,setPosts] = useState([]);
useEffect(() => {
getPosts()
.then(posts => setPosts(posts))
},[])
return (
<div>
{/* rendering of posts */}
</div>
)
}
const Personal = () => {
const [profileInfo,setProfileInfo] = useState({});
useEffect(() => {
getProfileInfo()
.then(profileInfo => setProfileInfo(profileInfo))
},[])
return (
<div>
{/* rendering of personal info.... */}
</div>
)
}
const Content = () => {
return (
<div className="Content">
<Switch>
<Route path="/posts" component={Posts} />
<Route path="/personal" component={Personal} />
</Switch>
</div>
)
}
another Component called sidebar controlls navigation between this components
That happens when you component is unmounted and you are trying to set the state. A simple way is to modify your useEffect to the following code to get rid of this error:
useEffect(() => {
let mounted = true;
getPosts().then(posts => {
if(mounted) setPosts(posts)
)
return () => { mounted = false };
},[])
So there are a couple of ways you could use for your situation and i have listed them starting with the simplest option.
The first option allows the browser to complete the request but only updates the state with response from the latest request;
useEffect(() => {
let cancelled = false;
function fetchData() {
fetch(url).then(response => response.json())
.then(result => {
if(!cancelled){
setData(result)
}
});
}
fetchData();
return () => {
cancelled = true;
};
}, [your deps])
Ignoring responses from former api calls
You might also want to ignore responses from former api calls.
// A ref to store the last issued pending request
const lastPromise = useRef();
useEffect(() => {
function fetchData(){
const currentPromise = fetch(url)
.then(response => response);
// store the promise to the ref
lastPromise.current = currentPromise;
// handle the result with filtering
currentPromise.then(result => {
if (currentPromise === lastPromise.current) {
setData(result);
}
});
}
// fire the api request
fetchData();
}, [your deps]);
Cancelling and ignoring
Sometimes it is better to cancel former api requests in-flight: the browser can avoid parsing the response and prevent some useless CPU/Network usage. fetch and axios support cancellation using AbortSignal:
useEffect(() => {
// Create the current request's abort controller
const abortController = new AbortController();
function fetchData(){
fetch(url, { signal: abortController.signal })
.then(data => data)
// Set the result, if not aborted
.then(
result => {
// IMPORTANT: we still need to filter the results here,
// in case abortion happens during the delay.
// In real apps, abortion could happen when you are parsing the json,
// with code like "fetch().then(res => res.json())"
// but also any other async then() you execute after the fetch
if (abortController.signal.aborted) {
return;
}
setState(newState);
},
);
}
fetchData();
// Trigger the abortion in useEffect's cleanup function
return () => {
abortController.abort();
};
}, [your deps]);
So you can just chose what suits your use case. Hope that helped.

React: Implementing a hook to fetch data and return loading state

I'm trying to implement a hook (useFetch(route, dependencies=[])) which will be used to fetch data and return a scalar value, loading, which is used to determine if the user is currently fetching new data.
This works as expected on the initial render (as the default state for loading is true).
The problem occurs on every subsequent render. Due to the loading state being currently set to false (after the initial render), the loading state will remain false for an extra render cycle before it is updated to true at the top of the useEffect().
How can I prevent that extra loading=false render cycle, so the component I use this useFetch() in is immediately aware that data is currently being fetched and that I can display the '...loading' text in the render() (without having to render twice to get to this point).
useFetch
function useFetch(route, successHandler, dependencies = []) {
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(route,
{
method: 'POST',
body: JSON.stringify({}),
headers: {"Content-Type": "application/json"}
})
.then(res => res.json())
.then(
() => {
// Set state data from component here
successHandler();
},
() => {/*...*/})
.finally(() => {
setLoading(false);
});
}, dependencies);
return loading;
}
Component
function Component(){
const [toggle, setToggle] = useState(false);
const [options, setOptions] = useState({});
const loading = useFetch('endpoint',
() => setOptions({foo: 'bar'}),
[toggle]);
return <div>
<button onClick={()=>{setToggle(!toggle)}}>Trigger useFetch()</button>
{loading ? '...loading': options.foo}
</div>;
}
Any help or design suggestions would be appreciated,
Your code has two mistakes:
const loading = useFetch('endpoint',
() => setOptions({foo: 'bar'}),
[toggle]);
The dependency array should be the variables that are used in the useEffect, in your case it can be [route]
[toggle] as a dependency parameter will send [false] or [true], depending on your state variable. Which won't be under control of the useEffect of the hook you created.
Check out this example, it might help you understand better:

Categories

Resources