Reputation: 113
I've got a problem with infinite loop inside useEffect
. I want to make new request only when id dependency changes. When I not pass data I've got a problem with setData
. My data state is not updated. When I pass data to the dependency I've got an infinite loop. How to fix it and why?
import React, {useEffect, useState} from 'react';
import LeftArrow from './LeftArrow';
import RightArrow from './RightArrow';
import SlideItem from './SlideItem';
const Slide = () => {
const [id, setId] = useState(0);
const [data, setData] = useState({currencies:[], isFetching:false});
const images = [
"https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
];
useEffect(()=> {
const getCurrentCurrency = async () => {
try{
setData({currencies: data.currencies, isFetching: true});
console.log("data", data.isFetching);
const currencyArr = [];
const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
const responseData = await response.json();
console.log(responseData);
const {EUR:euro ,CHF:franc, USD: dolar} = responseData.rates;
currencyArr.push(euro,franc,dolar);
console.log(currencyArr);
setData({currencies: currencyArr, isFetching: false});
console.log("currencies", data);
}
catch (e) {
console.log(e);
setData({currencies: data.currencies, isFetching: false});
}
};
getCurrentCurrency();
}, [id]);
const goToPrevSlide = () => {
// id === 0 ? setId(2) : setId(id-1);
}
const goToNextSlide = () =>{
// id === 2 ? setId(0) : setId(id+1);
}
return(
<div className="slide">
<div className="slide-wrapper"
style={{
transform: `translateX(500px)`,
transition: 'transform ease-out 0.45s'
}}
>
{
currencies.map((currency, i, images) => (
<SlideItem currency={currency} key={i} imageUrl={images[i]} />
))
}
</div>
<LeftArrow
goToPrevSlide={goToPrevSlide}
/>
<RightArrow
goToNextSlide={goToNextSlide}
/>
</div>
);
}
export default Slide;
Upvotes: 0
Views: 373
Reputation: 1413
Here, even though you are setting state with its own value, when the useEffect finishes it thinks you have updated state with a new value as data.currencies will be set during the useEffect, this causes the loop when adding it as a dependancy.
If you want to keep the code as you have you can add // eslint-disable-next-line
to the line before.
Reason: In this case as you know that you do not want to run this useEffect when data.currencies is changed (as that is the point of this useEffect)
Another option, since you are in fact trying to change currencies to a loading state, You can simply just change:
setData({currencies: data.currencies, isFetching: true});
-and-
setData({currencies: data.currencies, isFetching: false});
to be:
setData({isFetching: true});
-and-
setData({isFetching: false});
(Keep in mind that setState is not immediate as set state goes to the javascript queue so its best not to rely on this)
Full (slightly modified return statement) working code:
import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom'
const Slide = () => {
const [id, setId] = useState(0);
const [data, setData] = useState({currencies:[], isFetching:false});
const images = [
"https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
"https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
];
useEffect(()=> {
const getCurrentCurrency = async () => {
try{
setData({isFetching: true});
const currencyArr = [];
const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
const responseData = await response.json();
const {EUR:euro ,CHF:franc, USD: dolar} = responseData.rates;
currencyArr.push(euro,franc,dolar);
setData({currencies: currencyArr, isFetching: false});
}
catch (e) {
setData({isFetching: false});
}
};
getCurrentCurrency();
}, [id]);
const goToPrevSlide = () => {
id === 0 ? setId(2) : setId(id-1);
}
const goToNextSlide = () =>{
id === 2 ? setId(0) : setId(id+1);
}
return(
<div>
<div className="slide">
<div>
{id}
</div>
{JSON.stringify(data)}
<div>
<button onClick={goToPrevSlide}>Prev</button>
<button onClick={goToNextSlide}>Next</button>
</div>
</div>
</div>
);
}
Upvotes: 1
Reputation: 39320
Here is an example how you could do it with useDispatch, it's more code but you can take the reducer out of the component in a separate file and should put action types and action creators in a separate file as well.
const Slide = () => {
const [id, setId] = React.useState(0);
const [data, dispatch] = React.useReducer(
//you can move this function to a separate file and import it
(state, action) => {
const { type } = action;
if (type === 'LOAD') {//action types should be defined as constants
return {
...state,
loading: true,
};
}
if (type === 'RECEIVED') {
const currencies = action.payload;
return {
currencies,
loading: false,
};
}
if (type === 'ERROR') {
return {
...state,
loading: false,
};
}
return state;
}
);
const images = [
'https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
'https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
];
useEffect(() => {
const getCurrentCurrency = async () => {
try {
//should create action creator functions so you can do
//dispatch(load()) put the action creators in separate file
//usually called actions.js
dispatch({ type: 'LOAD' });
console.log('loading');
//not sure how this depends on id since you are not using id
// in the request or anywhere in the effect
const response = await fetch(
`https://api.exchangeratesapi.io/latest?base=GBP`
);
const responseData = await response.json();
console.log(responseData);
const {
EUR: euro,
CHF: franc,
USD: dollar,
} = responseData.rates;
const currencies = [euro, franc, dollar];
console.log(currencies);
dispatch({
type: 'RECEIVED',
payload: currencies,
});
console.log('currencies', currencies);
} catch (e) {
console.log(e);
dispatch({
type: 'ERROR',
});
}
};
getCurrentCurrency();
}, [id]);
//other stuff and return jsx
};
Before you dispatch you should probably check if the component is still mounted, if the component will not unmount then this is not a problem.
Upvotes: 0