Use Case for passing function as an dependency in useEffect in React - reactjs

I started learning to react and came across the code snippet where the function was passed as a dependency array in useEffect. I want to know the use case where such function is being passed as the dependency and why do we need to pass the function as a dependency?

First: This only makes sense if the code in the useEffect callback uses the function. So let's take that as a baseline. :-)
Fundamentally, you'd do that so the code in the useEffect callback is using the most up-to-date version of the function.
Here are a couple of examples where that would be important:
The function is a prop. Since your code doesn't know why it got a new version of the function, it's important to re-run the effect with the up-to-date version of the function.
The function uses state information it closes over (rather than using the callback form of a state setter). If you didn't re-run the effect with the updated function, the function would use stale state information. (But I wouldn't do it that way. Instead, I'd have the function use the callback form of the state setter.)
There are likely others, but they all boil down to ensuring the effect uses the most recent version of the function.

I am also learning React and this article helped me understand functions in the dependency array of useEffect.
Functions in dependency array of useEffect
A function is an object. It has its own identity like an object. A component re-renders each time on of its state changes. When a component re-renders, the function which is defined inside the component gets a new identity.
I made a bit clear code example here. The example from the article was unclear for me to understand. Use the console tab to see the console logs.

It depends on the use of the useEffect and the definition of the function. Basically, if you put a function inside a useEffect array, each time the function will change, or more accurately, it's reference, your effect will be called again, with the new function reference.
This is good in case you want to always use the latest function, but it can also be tricky. If the passed function is defined inside a component, it means that on every component render the function will be redefined, meaning your effect will be called on each component render. It can be heavy sometimes, depends on what your effect does.
It can still be avoided though, if the component in which the function is defined is using useCallback in order to memoize the function. This way, the function will have its own dependencies array, and will only be redefined (and change reference) when you decide it's needed.

Related

Do I understand React memoization correctly?

I’ve read the documentation about memoization techniques in React and have a feeling that I understand them correctly.
However, I see projects where useMemo and useCallback are applied in different ways.
I would like to confirm that I’m not missing anything, so I will describe my understanding below. Please, tell me if this makes sense or correct me if I’m wrong :)
useMemo
This hook is used to cache the result of some calculation between re-renders.
const cachedValue = useMemo(() => calculateValue(), dependencies)
It means that after the first run, the cachedValue is saved in memory and returned on subsequent renders without any calculations if dependencies remain the same. The cachedValue object on the will be the same (same reference) and would return true on Object.is().
Memoizing results of expensive computations improves performance because they are not calculated even if the component is re-rendered, but at the cost of running the hook itself. (Not sure about that)
Additionally, it is used to prevent re-renders when passing these values as props to a component memoized with React.memo(Component) or as dependencies to useEffect hook.
useCallback
This hook caches the function definition (not the result of its work) between re-renders.
const cachedHandler = useCallback(() => { doSomething(); }, dependencies);
The function is defined and saved (but not called) in the variable on the first run, and the same definition is returned on subsequent re-renders if dependencies remain the same.
Since the function is not called when this hook is used and no results are cached, there are no immediate performance gains. (Not sure about that)
The benefit is only when this cached function is passed as a prop to a component memoized with React.memo(Component) or as a dependency to useEffect.
When a memoized function is passed as a prop inside an inline function (<Component onSomething={() => cachedHandler()} /> there are no performance gains since a new anonymous function is created and passed every time. (Saw this usage a few times).
React.memo
A component wrapped in React.memo is cached and skips re-renders as long as its props remain the same. There is no need to memoize primitive values (strings, for example), but for objects and functions, you need to apply either useMemo or useCallback hooks.
General notes
A lot of projects and developers wrap values and functions in these hooks prematurely either by convention or as a precaution ("if we memoize everything, there is no need to think about it in the future and the cost is not so high").
The documentation and React developers advise against using these hooks until it is necessary (gives measurable performance gains) as using them is not free and adds cognitive complexity (harder to read and follow the code).
This description does not cover all the cases, but can be applied to most simple cases.

UseEffect: why is fetching data considered a side-effect but not accessing window.location?

I always thought of a side-effect as a read or write operation that is performed while rendering a functional component, and that accesses resources outside of those provided as props. Sort of like the definition of a pure function.
So when I needed to read window.location, I figured it had to be in a useEffect. Turns out the linter doesn't require that window.location be a dependency, so I guess it's ok to access window directly. This means my mental model of a React side-effect is wrong... But why?
If merely fetching data (which is a read operation) is considered a side-effect, then why isn't window.location?
So now I'm wondering, are functional components actually not really pure? Are they only "write pure" but not "read pure"?
How the useEffect or any other memoizing hooks(useCallback, useMemo,...) work works is:
Component re-renders
The dependency list is looked through to detect changes in any of them
If a change is detected, the function inside the useEffect is called
The issue with window.location or any other outer scope variables is that they do not initiate a component re-render to begin with.
This is the exact message on ESLint
Outer scope values like 'window.location.href' aren't valid
dependencies because mutating them doesn't re-render the component
And to answer your question, yes and no.
According this article :
A React component is considered pure if it renders the same output for
the same state and props
So if you add a dependency to an outer scope variable like window in your component, you can't call that component pure anymore.

Obeying react-hooks/exhaustive-deps leads to infinite loops and/or lots of useCallback()

My app has a userService that exposes a useUserService hook with API functions such as getUser, getUsers, etc. I use a Hook for this because the API calls require information from my Session state, which is provided by a React Context Provider.
Providing the getUsers functions to a useEffect call makes the react-hooks/exhaustive-deps eslint rule unhappy, because it wants the getUsers function listed as a dep - however, listing it as a dep causes the effect to run in an infinite loop, because each time the component is re-rendered, it re-runs the useUserService hook, which recreates the getUsers function.
This can be remedied by wrapping the functions in useCallback, but then the useCallback dependency array runs into a similar lint rule. I figure I must be doing something wrong here, because I can't imagine I'm supposed to wrap every single one of these functions in useCallback().
I've recreated the issue in Codesandbox.
1: Encounter eslint warning: https://codesandbox.io/s/usecallback-lint-part-1-76bcf?file=/src/useFetch.ts
2: Remedy eslint warning by sprinkling useCallback in, leading to another eslint warning: https://codesandbox.io/s/usecallback-lint-part-2-uwhhf?file=/src/App.js
3: Remedy that eslint rule by going deeper: https://codesandbox.io/s/usecallback-lint-part-3-6wfwj?file=/src/apiService.ts
Everything works completely fine if I just ignore the lint warning... but should I?
If you want to keep the exact API and constraints you've already chosen, that is the canonical solution — you need to ensure that everything "all the way down" has useCallback or useMemo around it.
So this is unfortunately correct:
I can't imagine I'm supposed to wrap every single one of these functions in useCallback()
There is a concrete practical reason for it. If something at the very bottom of the chain changes (in your example, it would be the "session state from React's context" you're referring to), you somehow need this change to propagate to the effects. Since otherwise they'd keep using stale context.
However, my general suggestion would be to try to avoid building APIs like this in the first place. In particular, building an API like useFetch that takes an arbitrary function as a callback, and then calling it in effect, poses all sorts of questions. Like what do you want to happen if that function closes over some state that changes? What if the consumer passes an inline function that's always "different"? If you only respected the initial function, would you be OK with the code being buggy when you're getting cond ? fn1 : fn2 as an argument?
So, generally speaking, I would strongly discourage building a helper like this, and instead rethink the API so that you don't need to inject a "way to fetch" into a data fetching function, and instead that data fetching function knows how to fetch by itself.
TLDR: a custom Hook taking a function that is then needed inside of an effect is often unnecessarily generic and leads to complications like this.
For a concrete example of how you could write this differently:
First, write down your API functions at top level. Not as Hooks — just plain top-level functions. Whatever they need (including "session context") should be in their arguments.
// api.js
export function fetchUser(session, userId) {
return axios(...)
}
Create Hooks to get any data they need from the Context.
function useSession() {
return useContext(Session)
}
Compose these things together.
function useFetch(callApi) {
const session = useSession()
useEffect(() => {
callApi(session).then(...)
// ...
}, [callApi, session])
}
And
import * as api from './api'
function MyComponent() {
const data = useFetch(api.fetchUser)
}
Here, api.fetchUser never "changes" so you don't have any useCallback at all.
Edit: I realized I skipped passing the arguments through, like userId. You could add an args array to useFetch that only takes serializable values, and use JSON.stringify(args) in your dependencies. You'd still have to disable the rule but crucially you're complying with the spirit of the rule — dependencies are specified. Which is pretty different from disabling it for functions, which leads to subtle bugs.

useEffect or useMemo for API functions?

which is the best hook for dispatching API calls in a component. Usually I use useMemo for calling the API on the first render, and useEffect if I need extra side effects, is this correct? Becouse sometimes I get the following error:
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
That happens when I route to a component and rapidly change to another one, it doesn't "affect" the general behaivour becouse if i go back to the previous component it renders as expected correctly. So how should I do it?
Calling an API is a side effect and you should be using useEffect, not useMemo
Per the React docs for useEffect:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
Per the React docs for useMemo:
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
Performing those side effects (and modifying state) during rendering or with useMemo is the reason you encounter the errors you mention.
basically I rather to use useEffect in componentDidMount manner, with no dependency like below
useEffect(() => {
// Api call , or redux async action here...
}, [])
for calling api's at component mount state.
most of the time i find my self using useMemo for memoising the data at functional Component render level, for preventing the variable re-creation and persist the created data between renders except the dependency changes.
but for the context of your question, there is a hook called useLayoutEffect which is primarily used for actions to happen before painting the DOM, but as i said basically most of the time in projects i find calling apis in a simple useEffect with no dependencies aka, the did mount of your component, in order to load the required data for component!
A bit late but, while everything mentioned above is completely true; the error
'''index.js:1 Warning: Cannot update a component (Inscriptions) while rendering a different component (PaySummary). To locate the bad setState() call inside PaySummary, follow the stack trace as described in ...''''
Has to do with the fact that the API call is Asynchronous and when you rapidly change the pages, the set state call (for updating the data returned from the API call I assume) is still waiting to be called after the data is returned from the API. So, you have to always clean up your Async functions in useEffect to avoid this error.

What can be a dependency for React hooks?

Looking at the docs, there is a note at the bottom of this section [edited link] that makes it seem like only props or state should be used in a hook dependency list. However, when a "complex expression" on props or state is used in the list, the eslint plugin gives the following:
React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked. eslint(react-hooks/exhaustive-deps)
This has me thinking about what is allowed to be used as a dependency. Can we use a local variable that is calculated from props? Do we need to create some sort of new state variable or ref in this case? I'm not certain if hooks are executed in place within the component (so local variables are available) or are hoisted out of the context of the render (so we have to use only state, props, or other hook values like refs/memos).
Examples
A component has a prop, data, that is an object.
data: {
name: 'name',
id: 2
}
1) It looks like data.name can be used in the dependencies. But can we use a local variable that is set to the property?
const { name } = data;
useEffect(fn, [name]);
2) Would we be able to use a variable that is calculated by a prop in a dependency array?
const isOdd = Boolean(data.id % 2);
useEffect(fn, [isOdd]);
Both cases seem to work with small tests. I'm not knowledgable enough with Hooks to know if it's breaking some rules that would leave the results as indeterminate.
Optimizing Performance by Skipping Effects may help understanding hook dependencies.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
The important bit, "...all values from the component scope...", meaning any value within the component scope used within the hook.
Q: Can we use a local variable that is calculated from props?
Yes
Q: Do we need to create some sort of new state variable or ref in this case?
No
Q: I'm not certain if hooks are executed in place within the component (so local variables are available) or are hoisted out of the context of the render (so we have to use only state, props, or other hook values like refs/memos)
AFAIK, they are just functions with special rules within react. They are invoked within the scope of the functional component.

Resources