React Hooks: useEffect with dependecy also called when component mounts - reactjs

In React, you can use useEffect() hook with a dependency on it.
const [data, setData] = useState();
useEffect(() => {
console.log(data.test.test);
}, [data]);
So I would assume that this useEffect is just called when the data is changed.
So let´s assume I would make an api call and use setData(response.data).
Then the useEffect() should be triggerd, since the data state is a dependecy.
Then the console.log(data.tes.test) would be working, since I got the right data.
However, I get an undefined error, which comes from the fact that useEffect() is also called initially.
How can I avoid this? This is pretty annoying and in my mind not a good design with regard to adding a dependency.

You have two solutions. As #Woohaik has already mentioned, checking for data existence in the useEffect. I'd implement a guard:
useEffect(() => {
if (!data) return
console.log(data.test.test)
}, [data])
or you could make yourself acquainted with optional chaining (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining):
useEffect(() => {
console.log(data?.test?.test)
}, [data])

Related

useEffect trigger explation [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
Closed 5 months ago.
Can someone explain why this even triggers the useEffect? The setState function in the useState hook always seems to be a function. Its not undefined (at initialization) at any point in time?
const [state, setState] = useState(0)
useEffect(() => {console.log('triggers')}, [setState])
I'm away of the caveats with React 18s mounting/unmounting, but since setState never changes the effect should not fire? What am I missing here?
The above code is basically the same as writing:
useEffect(() => {...}, [])
It will call on the initial render only
React will call the callback function of useEffect on the initial render and when any of the dependency changes from dependency array.
useEffect(() => {
console.log("triggers");
}, [setState]);
CODESANDBOX DEMO
So, The callback function will trigger on the inital render and when the setState changes (which will never change)
Long story short, useEffect will always run when the component first loads, even if you have a dependency list,
useEffect(()=> { console.log("hello") }) // will log "hello"
useEffect(()=> { console.log("hello") } []) // will log "hello"
useEffect(()=> { console.log("hello") } [x]) // will log "hello"
useEffect(()=> { console.log("hello") } [x, y, z]) // will log "hello"
if you only want your useEffect to fire if the dependency element in the dependency list changes, you need to set some logic inside, for instance, for your case:
const [state, setState] = useState(0)
useEffect(() => {
if(!state) return
console.log('triggers')
}, [state])
and the same if you have something else to check, for instence if you have the initial state set to an empty array, you would do the condition like if(!state.length) return, and so on.
This is kinda sucks when you have multiple elements in the dependency list, that's why the react developers recommend having mutliple useEffects in your component for such case, where each useEffect() handles a piece of logic.

React make an async call on data after retrieving it from the state

I need to make an async call after I get some data from a custom hook. My problem is that when I do it causes an infinite loop.
export function useFarmInfo(): {
[chainId in ChainId]: StakingBasic[];
} {
return {
[ChainId.MATIC]: Object.values(useDefaultFarmList()[ChainId.MATIC]),
[ChainId.MUMBAI]: [],
};
}
// hook to grab state from the state
const lpFarms = useFarmInfo();
const dualFarms = useDualFarmInfo();
//Memoize the pairs
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [chainIdOrDefault, lpFarms, dualFarms]);
//Grab the bulk data results from the web
useEffect(() => {
getBulkPairData(pairLists).then((data) => setBulkPairs(data));
}, [pairLists]);
I think whats happening is that when I set the state it re-renders which causes hook to grab the farms from the state to be reset, and it creates an infinite loop.
I tried to move the getBulkPairData into the memoized function, but that's not meant to handle promises.
How do I properly make an async call after retrieving data from my hooks?
I am not sure if I can give you a solution to your problem, but I can give you some hints on how to find out the cause:
First you can find out if the useEffect hook gets triggered too often because its dependency changes too often, or if the components that contains your code gets re-mounted over and over again:
Remove the dependency of your useEffect hook and see if it still gets triggered too often. If so, your problem lies outside of your component.
If not, find out if the dependencies of your useMemo hook change unexpectedly:
useEffect(()=>console.log("chainIdOrDefault changed"), [chainIdOrDefault]);
useEffect(()=>console.log("lpFarms changed"), [lpFarms]);
useEffect(()=>console.log("dualFarms changed"), [dualFarms]);
I assume, this is the most likely reason - maybe useFarmInfo or useDualFarmInfo create new objects on each render (even if these objects contain the same data on each render, they might not be identical). If so, either change these hooks and add some memoization (if you have access to your code) or narrow down the dependencies of your pairLists:
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [lpFarms[chainIdOrDefault], dualFarms[chainIdOrDefault]]);

Difference between useEffect and useMemo

I'm trying to wrap my head around what exactly the useMemo() React hook is.
Suppose I have this useMemo:
const Foo = () => {
const message = useMemo(() => {
return readMessageFromDisk()
}, [])
return <p>{message}</p>
}
Isn't that exactly the same as using a useState() and useEffect() hook?
const MyComponent = () => {
const [message, setMessage] = useState('')
useEffect(() => {
setMessage(readMessageFromDisk())
}, [])
return <p>{message}</p>
}
In both cases the useMemo and useEffect will only call if a dependency changes.
And if both snippes are the same: what is the benefit of useMemo?
Is it purely a shorthand notation for the above useEffect snippet. Or is there some other benefit of using useMemo?
useEffect is used to run the block of code if the dependencies change. In general you will use this to run specific code on the component mounting and/or every time you're monitoring a specific prop or state change.
useMemo is used to calculate and return a value if the dependencies change. You will want to use this to memoize a complex calculation, e.g. filtering an array. This way you can choose to only calculate the filtered array every time the array changes (by putting it in the dependency array) instead of every render.
useMemo does the same thing as your useEffect example above except it has some additional performance benefits in the way it runs under the hood.

Handling API calls using useEffect vs using useCallback

This is very likely a dumb question -- I have the understanding that anything that triggers a side-effect should be handled with useEffect. I was wondering if that understanding is correct. Particularly in the context of making an API call -- is it good to make API calls in a useCallback hook?
If you want to do it based on some kind of prop or state change, use the useEffect hook, e.g.,
useEffect(async ()=> {
const user = await userApi.get(props.id) // Api call here
setUser(user)
}, [props.id]})
If you want to do so on a button click (or any event),
const handleClick = () => {
const user = await userApi.get(props.id)
setUser(user)
}
useCallback isn't really relied on for api calls or side-effects. useCallback is basically storing a "version" of a function, based on dependencies. When a dependency changes, you get a new function, because what it returns will be different, e.g.,
// props.id = 1
const doSomethingCallback = useCallback(() => {
/* do something with props.id */
}, [props.id])
While props.id === 1, doSomethingCallback will always reference the function as it was declared on the first render. If props.id changes to 2, useCallback will reference a new function. So, if doSomethingCallback was a dependency of a useEffect, when props.id changed to 2, useCallback would point to a new function, which would then get noticed by the useEffect and run whatever was in it, e.g.,
useEffect(() => { /* side-effect stuff */}, [doSomethingCallback])

Why React expects me to add setters as useEffects dependencies

I am trying to use an async function inside a useEffect callback.
There is a behavior i don't understand (in my case related to the throttle function from lodash).
I don't get what is happing, and how to solve it, here is a sample of the code:
import throttle from 'lodash/throttle';
const myRequestWrapped = throttle(myRequest, 300);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name) // No warning
myRequestWrapped(name); // React Hook useEffect has a missing dependency: 'myRequestWrapped'. Either include it or remove the dependency array
}, [ name ]);
If i add myRequestWrapped as a dependency, i have an infinite loop (the effect is triggered continuously).
I guess the throttle method works with a timer and it returns a different result at every run so i can understand why the infinite loop.
But i really don't understand why React wants it as a dependency (especially that it works without adding it !).
What is the logic?
Why myRequestWrapped and not myRequest?
Should i ignore the warning or do you know a clean way to solve that?
Thanks.
Its not React that wants you to add myRequestWrapped as a dependency but its eslint.
Also you must note that ESLint isn't aware of the programmers intention so it just warns the user if there is a scope of error being made.
Hooks heavily rely on closures and sometimes its difficult to figure out bugs related to closures and that is why eslint prompts if there is a case of a fucntion of variabled used within useEffect that might reflect the updated values.
Of course the check isn't perfect and you could carefully decide whether you need to add a dependency to useEffect or not.
If you see that what you wrote is perfectly correct. You can disable the warning
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ name ]);
Also you must not that throttle function cannot be used within render of functional componentDirectly as it won't be effective if it sets state as the reference of it will change
The solution here is to use useCallback hook. Post that even if you add myRequestWrapped as a dependency to useEffect you won't be seeing an infinite loop since the function will only be created once as useCallback will memoize and return the same reference of the function on each render.
But again you must be careful about adding dependency to useCallback
import throttle from 'lodash/throttle';
const Comp = () => {
const myRequestWrapped = useCallback(throttle(myRequest, 300), []);
const [name, setName] = useState('');
useEffect(() => {
myRequest(name);
myRequestWrapped(name);
}, [ name ]);
...
}
Shubham is right: it's not REACT, but ESLint instead (or TSLint, depending on the Linter you are using).
If I may add, the reason why the Linter suggest you to add myRequestWrapped is because of how the closures work in JavaScript.
To let you understand, take this easier example (and I need this because I would need to know what it's inside myRequestWrapped:
const MyComp = props => {
const [count, setCount] = React.useState(0);
const handleEvent = (e) => {
console.log(count);
}
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, []);
return <button onClick={e => setCount(s => s +1)}>Click me</button>
}
So, right now, when MyComp is mounted, the event listener is added to the document.body, and anytime the click event is triggered, you call handleEvent, which will log count.
But, since the event listener is added when the component is mounted, the variable count inside handleEvent is, and always will be, equal to 0: that is because the instance of handleEvent created when you added the event listener is just one, the one that you associated with the event listener.
Instead, if you would write the useEffect like this:
React.useEffect(() => {
document.body.addEventListener('click', handleEvent);
return () => { document.body.removeEventListener('click', handleEvent); }
}, [handleEevent]);
Anytime the handleEvent method is updated, also your event listener is updated with the one handleEvent, thus when clicking on the document.body you will always log the latest count value.

Resources