Axios call gives infinite loop - javascript

I need to fetch some data and set the state, but it gives me infinite loop and I don't know how to fix this.
example in my Routes.tsx:
// get all posts
useEffect(() => {
Axios.get('/api/posts', config)
.then(res => setAllPosts(res.data))
.catch(err => console.log(err))
}, [config]);
if I don't put 'config' in dependency, it will show results only on refresh, but then it wont give me infinite loop.
here is my project: https://github.com/marinavrataric/social_network

Try using useStata[] with useEffect[]
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await axios.get('/api/posts', config)
.then(res => setAllPosts(res.data))
.catch(err => console.log(err))
setData(result);
}, []);
return (
<ul>
{data.posts.map(item => (
// stuff
))}
</ul>
);

As you are calling your API in useEffect and also updating the state in useEffect too, and useEffect without dependency is called every time the component renders, so here is the loop:
** call the useEffect > call API> updating state using setState> as the state is update, call the useEffect **
So you need to add a dependency for useEffect which is not changed by the setState. And then, whenever the value of the dependentis changed, the API will be called.
BTW, its better not to implement API call in useEffect. Rather You should try using readux and thunk.

Related

React.js useEffect alternative or another hook maybe?

i have another "movie database" application in react. At the mount it renders movies based on api key which is set to "new movies". Then i have useEffect which update movie list based on searchbar and its value. Problem is, it renders new movies and just after that renders movies based on searchbar value which is empty. I know that useEffect is running on mount. What is best practice to use it this way? Or is there any better hook for this particular use? Thank you.
React.useEffect(() => {
fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}, [searchValue]);
Hey #JSpadawan best practice is to use filter like-
const [data,setData] = useState()
useEffect(()=>{
const apiData = fetch(APILINK).then((res)=>res.json()).catch(err)
if(apiData.length>0){
setData(apiData)
}
},[])
This will set the data of api.. now use filter like-
const [searchValue, setSearchValue]= useState()
const filterData = data && data.filter((data)=>data.includes(searchValue))
After this you can use filterdata any where you want. If you still facing issue just lemme know, i will help you more.
Thanks
You can set the movies with the useEffect, but I would recommend having a secondary useEffect that would be for the seach bar. You should try to avoid making tons of API calls as the more you make, the more it will slow down the application. So have one useEffect that will set the data, and a second one that you can filter out the ones that match the search bar.
If the searchValue is updated every key stroke then searching for a movie with a long title would create tons of API calls. You could put the fetch call in an async/await function so you get the data, then set it, then you can filter with a different effect.
useEffect(() => {
const getData = async () => {
await fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}
getData();
}, []); // empty array will run once on component load, or add a dependency of when you want it to re-call the api
Then a second useEffect for filtering
useEffect(() => {
let searchTextFilter = movies.filter(i => {
return (!searchValue || (searchValue && i.Title.toLowerCase().includes(searchFilter?.toLowerCase() )))
});
}, [searchValue])

using state from a context provider within my useEffect dependency array causes an infinite loop

I am using the shopify-buy SDK, which allows me to retrieve the current cart of the user. I am trying to store that cart in my CartProvider which is then used in my Cart component. The problem is when I retrieve information from the cart it's acting a little slow so my component needs to be updated when the state changes, currently I have the following in my getShopifyCart function which is located in my CartProvider.
const [cartItems, setCartItems] = useState([])
const getShopifyCart = () => {
return client.checkout
.fetch(currentVendor.cartId)
.then((res) => {
const lineItemsData = res.lineItems.map((item) => {
return {
title: item.title,
quantity: item.quantity,
}
})
setCartItems(lineItemsData)
setLoading(false)
})
.catch((err) => console.log(err))
}
In my Cart component I have the following useEffect.
useEffect(() => {
getShopifyCart()
}, [cartItems])
But this causes an infinite loop, even though the cartItems state isn't changing.
You are setting the state cartItems inside getShopifyCart which you are calling inside a useEffect which has cartItems as a dependency. Even though the content of the data has not changed, you are creating a new object, hence its hash has changed as well which causes the useEffect to be called again.
If you want to initially fetch the data and set the state, then you need to pass an empty dependency array.
useEffect(() => {
getShopifyCart()
}, [])

useEffect causing it to call the method to get posts way too many times. I only want to get the posts when my query changes

I am trying to call the reddit API. The post titles are showing up, but I want them to rerender when my query changes. I just want to know how to call a method when a piece of my state changes(aka my query). I’m using useEffect from react to do it but that calls it whenever anything changes in the component, causing it to call the method to get posts way to many times. I only want to get the posts when my query changes.
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
function Results()
{
const query = useSelector(state => state.query);
const results = useSelector(state => state.results);
const dispatch = useDispatch();
let fetchResults = () =>
{
let postTitles = [];
let postSrcs = [];
fetch('https://www.reddit.com/r/' + query + '.json')
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.data.children.length; ++i) {
if (body.data.children[i].data.post_hint === 'image')
{
let img_url = body.data.children[i].data.url_overridden_by_dest;
postSrcs.push(img_url);
}
let title = body.data.children[i].data.title;
postTitles.push(title);
}
dispatch({type: "QUERY_RESULTS", payload: postTitles})
}).catch((err) => {
console.log(err)
});
}
useEffect(() => {
fetchResults();
console.log("use effect triggered")
})
return (
<>
<h1>Query: {query}</h1>
{ !results
? <h1>No Results</h1>
: results.map(p => <h6> {p} </h6>)
}
</>
)
}
export default Results;
For example in the console log that tells me when use effect is triggered. and when i search for a post the use effect triggered is stacking up.
useEffect has a differents mode. You can check how to use in official document https://reactjs.org/docs/hooks-reference.html#useeffect
So the main you must know is 3 things
useEffect is the last render in react. So first render a components and read other code when it finish useEffect run.
useEffect may run code only one time adding []. for example
useEffect ( () => {
...code
}, [])
This code will run only one time.
useEffect may run code watching variables adding variables into []. For example
useEffect ( () => {
...code
}, [ count, name , ... ])
This code will run first time and later would run if count or name change
To achieve that you need to prevent useEffect to be called on any changes, and only once the query changes.
NOTE: Since you're using dispatch within fetchResults, it's better to make sure that dispatch is ready before calling fetchResults.
Your useEffect may look like the following to achieve that:
useEffect(() => {
// To prevent call fetchResults if dispatch only is changed
if (query) {
fetchResults();
console.log("use effect triggered");
}
}, [dispatch, query]);
Hooks like useEffect are used in function components. The Class component comparison to useEffect are the methods componentDidMount, componentDidUpdate, and componentWillUnmount.
useEffect will run when the component renders, which might be more times than you think.
So useEffect takes a second parameter
The second param is an array of variables that the component will check to make sure changed before re-rendering. You could put whatever bits of props and state you want in here to check against.
In your case add [query] as a second para:
useEffect(() => {
fetchResults();
console.log("use effect triggered")
},[query])
https://css-tricks.com/run-useeffect-only-once/

state is not updating using React hook

This is a function which has to be called when the component mounts on DOM
const [dashboardData, setDashboardData] = useState('');
const loadDashboardData = () => {
console.log("Loading Dashboard Data ", campaign);
Utils.request({
url: `campaign/user/info`
}).then(
res => {
console.log("dashboard data" , res.data)
setDashboardData(res.data);
},
err => console.log(err)
)
}
useEffect(() => {
loadDashboardData();
console.log("campaigndata",dashboardData);
}, []);
when I console dashboardData in useEffect, it shows nothing but a string i.e campaigndata which I passed as the first argument in console.log. what I think that my dashboard state variable is not getting updated
Answer
Write another useEffect just for dashboardData.
useEffect(() => {
console.log("campaigndata",dashboardData);
}, [dashboardData]);
Explanation
Your useEffect is taking an empty array as the second argument, which makes it run only the first time (as DidMount), so it won't re-run on component re-render after changing the state, that's why it is showing empty string as the initial state.
Writing another useEffect for the variable dashboardData will run as many times as the dashboardData changes.
You can just pass the 'dashboard' to the existing useEffect as dependency. The existing useEffect will work as both componentDidMount and componentDidUpdate.
So no need to write another useEffect.
useEffect(() => {
loadDashboardData();
console.log("campaigndata",dashboardData);
}, [dashboardData]);

React hooks - useState() is not re-rendering the UI with the new state updates

I am trying out the new React Hooks, and I am a little stuck as the UI is not updating when the local state is updated. Here is my code,
import React, { useState, useEffect } from 'react';
import Post from './Post'
import PostForm from './PostForm';
import axios from 'axios';
function PostsList() {
const [posts, setPosts] = useState([]);
// setting up the local state using useEffect as an alternative to CDM
useEffect(() => {
axios.get('...')
.then(res => {
// the resposne is an array of objects
setPosts(res.data)
})
})
const handleSubmit = (data) => {
// the data I am getting here is an object with an identical format to the objects in the posts array
axios.post('...', data)
.then(res => {
// logging the data to validate its format. works fine so far..
console.log(res.data);
// the issue is down here
setPosts([
...posts,
res.data
])
})
.catch(err => console.log(err))
}
return (
<div>
<PostForm handleSubmit={handleSubmit} />
<h3>current posts</h3>
{ posts.map(post => (
<Post key={post.id} post={post} />
)) }
</div>
)
}
when I submit the form, the UI flickers for a split second and then renders the current state without the new update, it seems that something is preventing it from re-rendering the new state.
If more code/clarification is needed please leave a comment below.
thanks in advance.
alright, problem solved with the helpful hint from #skyboyer,
so what happened initially is, the useEffect() acts like componentDidMount() & componentDidUpdate() at the same time, that means whenever there is an update to the state, the useEffect() gets invoked, which means resetting the state with the initial data coming from the server.
to fix the issue I needed to make the useEffect() renders the component only one time when it's created/rendered as opposed to rendering it every time there is an update to the state. and this is done by adding an empty array as a second argument to the useEffect() function. as shown below.
useEffect(() => {
axios.get('...')
.then(res => {
setPosts(res.data)
})
}, [])
thanks :)

Categories

Resources