useeffect is running for infinite time eventhough dependencies are not changing - reactjs

I have my useeffect defined as follows- I am trying to get some values
from backend based on the dependencies changes. However it's creating
an infinite loop.
useEffect(() => {
if (
model &&
model.mortgage &&
model.mortgage.borrowingsStartDate &&
model.mortgage.borrowingsRepaymentFrequency &&
model.mortgage.borrowingsAnnualInterestRate &&
model.mortgage.borrowingsPrinciple &&
model.mortgage.borrowingsTermInYears
) {
loanRepaymentAndInterest();
} else {
setRepaymentAmount(0);
}
}, [
model.mortgage.borrowingsStartDate,
model.mortgage.borrowingsRepaymentFrequency,
model.mortgage.borrowingsAnnualInterestRate,
model.mortgage.borrowingsPrinciple,
model.mortgage.borrowingsTermInYears
]);
I have declared my function as-
const [repaymentAmount, setRepaymentAmount] = useState(0);
const loanRepaymentAndInterest = async () => {
const payload = {
amount: {
currency: model.mortgage.borrowingsCurrency,
value: model.mortgage.borrowingsPrinciple
},
freq: model.mortgage.borrowingsRepaymentFrequency,
interestRate: model.mortgage.borrowingsAnnualInterestRate,
startDate: model.mortgage.borrowingsStartDate,
term: model.mortgage.borrowingsTermInYears
};
await api
.post(`/loan/repayment`, {
...payload
})
.then(data => {
setRepaymentAmount(data.data.repaymentAmount.value);
})
.catch(error => console.log(error));
};
The forms has been managed by react final forms. The function will run inside useeffect whenever the dependencies change. The dependencies are just the form field values which are stored inside model object. I am trying to get some values from backend based on those changed dependencies values. Eventhough the dependencies do not change useeffect is running for infinite time. The api is called infinitely.

Related

Stop useEffect if a condition is met

I have a useEffect set up how I thought would only run once on initial render but it continues to rerun.
This breaks a function that is supposed to set a piece of state to true if a condition is truthy and show appropriate UI.
This sort of works but then the useEffect runs again flicks back to false immediately. I am also using a use effect to check on first render if the condition is truthy and show appropriate UI if so.
Basically when setIsPatched is true I don't want the useEffect to rerun because it flicks it back to false and breaks the UI
Here is the function:
const [isPatched, setIsPatched] = useState<boolean>(false);
useEffect(() => {
getApplied(x);
}, []);
const getApplied = (x: any) => {
console.log(x);
if (x.Log) {
setIsPatched(true);
return;
} else {
setIsPatched(false);
}
};
I also pass getApplied() to child component which passes a updated data to the function for use in this parent component:
const updatePatch = async (id: string) => {
//check if patch already in db
const content = await services.data.getContent(id);
const infoToUpdate = content?.data[0] as CN;
if (!infoToUpdate.applyLog && infoToUpdate.type == "1") {
// add applyLog property to indicate it is patched and put in DB
infoToUpdate.applyLog = [
{ clID: clID ?? "", usID: usID, appliedAt: Date.now() },
];
if (content) {
await services.data
.updateX(id, content, usId)
.then(() => {
if (mainRef.current) {
setDisabled(true);
}
});
}
getApplied(infoToUpdate);
} else {
console.log("retrying");
setTimeout(() => updatePatch(id), 1000); // retries after 1 second delay
}
};
updatePatch(id);
}

useState not updating rendered values after getting document snapshots

I am having a problem assigning data to useState by fetching data using reference type value from firebase.
const [preOil, setPreOil] = useState([]);
const [oilChange, setOilChange] = useState([]);
useEffect(() => {
getDocs(query(collection(db, "oil"), orderBy("timestamp"))).then(
(snapshot) => {
setPreOil(
snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}))
);
}
);
}, []);
useEffect(() => {
let current = preOil.length > 0 ? [...preOil] : [];
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
}, [preOil]);
In the first useEffect, the data is fetched successfully in this form,
preOil = {
id:"RxbAOAIs8d3kGOHNskJ4",
oilReference: Ta {converter: null, _key: ut, type: 'document', firestore: Na},
customerName: "USAMA",
}
In the second useEffect based on [preOil], I need to reassign the oilReference with the data fetched from firestorm through its reference(oilReference), The data is successfully fetched from the database and assigned to current but The main problem is when I set to state setOilChange(current) it updates my oilChange state when I inspect in inspect tools in chrome but in JSX the changes don't reflect
I am updating the state in useEffect
I am having desired data assigned in a local variable and assign that variable to the state
Then What am I missing?
In your second useEffect(), more specifically, in
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
You are mutating the content of the current variable. This mutation, because it is async, will happen after the setOilChange call. Such mutation will thus not trigger a re-render.
What you need is to instead first wait for all the docs to be loaded and only after that set the state. Example:
const _docs = current.map((_oil, i) => {
return getDoc(_oil.oilReference).then((oilRef) => { // changed here
return { // changed here
...current[i], // changed here
oilReference: oilRef.data() // changed here
} // changed here
}); // changed here
});
Promise.all(_docs).then(() => {
setOilChange(_docs);
});
Also notice I didn't mutate current, rather I returned a new object. This is not mandatory but just a best practice overall.

Array isn't filled with object in Javascript

So after the content is loaded in the website I'm trying to call a function to fill [fields] (State) with the events but something Isn't working.
Here is what I mean:
const [events, setEvents] = useState([]);
useEffect(() => {
if (orders.orders !== null && orders.isLoading !== true)
orders.orders.map((order) =>
setEvents([
...events,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
},
])
);
setLoading(false);
}, [orders]);
So when I console log the events, I get [] (empty), but if i console.log the object it works.
I'm not sure what you are trying to achieve with the code but looks like your useEffect has a missing dependency of events. Since you are also updating events with setEvents, you should update with
setEvents(prev=>[
...prev,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
}
])
to avoid an infinite loop.
You are directly updating state so it is shown in console but in react rerender should be done to update state so pass parameter for setEvents and spread parameter but not directly events because you cannot directly update state.
const [events, setEvents] = useState([]);
useEffect(() => {
if (orders.orders !== null && orders.isLoading !== true)
orders.orders.map((order) =>
setEvents(prev => [
...prev,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
},
])
);
setLoading(false);
}, [orders]);

useEffect and redux a cache trial not working

I have a rather complex setup and am new to React with Hooks and Redux.
My setup:
I have a component, which when first mounted, should fetch data. Later this data should be updated at a given interval but not too often.
I added useRef to avoid a cascade of calls when one store changes. Without useEffect is called for every possible change of the stores linked in its array.
The data is a list and rather complex to fetch, as I first have to map a name to an ID and
then fetch its value.
To avoid doing this over and over again for a given "cache time" I tried to implement a cache using redux.
The whole thing is wrapped inside a useEffect function.
I use different "stores" and "reducer" for different pieces.
Problem:
The cache is written but during the useEffect cycle, changes are not readable. Even processing the same ISIN once again the cache returns no HIT as it is empty.
Really complex implementation. It dawns on me, something is really messed up in my setup.
Research so far:
I know there are libs for caching redux, I do want to understand the system before using one.
Thunk and Saga seem to be something but - same as above plus - I do not get the concept and would love to have fewer dependencies.
Any help would be appreciated!
Component - useEffect
const calculateRef = useRef(true);
useEffect(() => {
if (calculateRef.current) {
calculateRef.current = false;
const fetchData = async (
dispatch: AppDispatch,
stocks: IStockArray,
transactions: ITransactionArray,
cache: ICache
): Promise<IDashboard> => {
const dashboardStock = aggregate(stocks, transactions);
// Fetch notation
const stockNotation = await Promise.all(
dashboardStock.stocks.map(async (stock) => {
const notationId = await getXETRANotation(
stock.isin,
cache,
dispatch
);
return {
isin: stock.isin,
notationId,
};
})
);
// Fetch quote
const stockQuote = await Promise.all(
stockNotation.map(async (stock) => {
const price = await getQuote(stock.notationId, cache, dispatch);
return {
isin: stock.isin,
notationId: stock.notationId,
price,
};
})
);
for (const s of dashboardStock.stocks) {
for (const q of stockQuote) {
if (s.isin === q.isin) {
s.notationId = q.notationId;
s.price = q.price;
// Calculate current price for stock
if (s.quantity !== undefined && s.price !== undefined) {
dashboardStock.totalCurrent += s.quantity * s.price;
}
}
}
}
dispatch({
type: DASHBOARD_PUT,
payload: dashboardStock,
});
return dashboardStock;
};
fetchData(dispatch, stocks, transactions, cache);
}
}, [dispatch, stocks, transactions, cache]);
Action - async fetch action with cache:
export const getXETRANotation = async (
isin: string,
cache: ICache,
dispatch: AppDispatch
): Promise<number> => {
Logger.debug(`getXETRANotation: ${isin}`);
if (CACHE_ENABLE) {
const cacheTimeExceeding = new Date().getTime() + CACHE_NOTATION;
if (
isin in cache.notation &&
cache.notation[isin].created < cacheTimeExceeding
) {
Logger.debug(
`getXETRANotation - CACHE HIT: ${isin} (${cache.notation[isin].created} < ${cacheTimeExceeding} current)`
);
return cache.notation[isin].notationId;
}
}
// FETCH FANCY AXIOS RESULT
const axiosRESULT = ...
if (CACHE_ENABLE) {
Logger.debug(`getXETRANotation - CACHE STORE: ${isin}: ${axiosRESULT}`);
dispatch({
type: CACHE_NOTATION_PUT,
payload: {
isin,
notationId: axiosRESULT,
created: new Date().getTime(),
},
});
}
//FANCY AXIOS RESULT
return axiosRESULT;
}

How to get rid of endless requests?

please help with useCallback, how to call it so that useEffect does not send endless requests to the server? Now it sends endless requests, I know that useCallback is needed for this, but I don’t understand how to use it
// Get data
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
}, [api, leagues, sidebars, countries, onTest]);
// The 'onTest' function makes the dependencies of useEffect Hook (at line 29) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'onTest' definition into its own useCallback() Hook
// test
const onTest = () => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
};
Req:
I'm assuming you want to load data once, normally done with componentDidMount.
2 useEffect fired only once, running in parralel
useEffect(() => {
api.getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
api.getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
useEffect running only on data changes
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
}
}, [leagues, countries]);
Update
api defined earlier ...
const api = new Services();
of course changes on every render but should not be taken as dependency as it's always uses the same urls - they are not parametrized. No meaningful (affecting data) changes - don't care.
If api is not passed to rendered components (ref cange could force rerenderings) then there is no need to take care about it.
You can get rid of this definition by
useEffect(() => {
new Service().getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
new Service().getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
Even if it looks as unnecessary duplicating instances it can be more optimal when component is frequently rerendered - like in open-close view changes.
You're in an infinite loop because your effect depends on values you change in it: each time you fetch some data from the api you change leagues and countries states which leads to running the effect again.
Split the effects so that the requests effect does not depend on the values it changes:
useEffect(() => {
api.getTestLeagues().then(data => setLeagues(data));
api.getTestCountries().then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest();
}
}, [leagues, sidebars, countries, onTest]);
Also:
Define api outside of your component (above) so the value stays stable.
You can wrap onTest in a useCallback before your effects, like #Ramesh suggested.
Try wrapping onTest with useCallback like this:
const onTest = useCallback(() => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
},[]);
The second argument to useCallback is an array of dependencies similar to useEffect.
BTW this will memorize the function so that a new function doesn't get created on every render.
update
It is better to split the useEffect so that each useEffect just focuses on executing something when their dependencies are changed.
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
},[onTest, sidebars, leagues, countries])
When you send a request that changes the state and also use the same state as a dependency that'll again send a request(because the value of the state changes) it'll cause an infinite loop.
update 2
The issue might be with the api class instance because on every new render a new instance is created.
You said you were doing something like this const api = new Services(); so wrap that with useMemo like this:
const api = useMemo(() => {
return new Services();
})

Resources