useCallback: When to use it? Is there any drawback / performance issue? - javascript

There are articles related to what is useCallback and to use it when necessary. But in a big project it's very difficult to understand when to start using this.
So, if I use it for every use case, will it hit the performance of the application.

useCallback and useMemo are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. Usually you want to work with useCallback to ensure a stable signature to a prop which you know how will change and React doesn't.
A function(reference type) passed via props for example
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [])
}
Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect, so let's declare setParentState as a dependency to be checked by React
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [setParentState])
}
Now this effect runs on each render, not only on mounting, but on each update. This happens because setParentState is a function which is recreated every time the function Component gets called. You know that setParentState won't change it's signature overtime so it's safe to tell React that. By wrapping the original helper inside an useCallback you're doing exactly that (by adding another dependency check layer).
const Component = ({ setParentState }) =>{
const stableSetter = useCallback(() => setParentState(), [])
useEffect(() => setParentState('mounted'), [stableSetter])
}
There you go. Now React knows that stableSetter won't change it's signature inside the lifecycle therefore the effect do not need run unecessarily.
On a side note useCallback it's also used like useMemo, to optmize expensive function calls (memoization).
The two main purposes of useCallback are
Optimize child components that rely on reference equality to prevent unnecessary
renders. Font
Memoize expensive computing calculations
Is there any drawback/performance?
useCallback is mainly used to optmize performance by changing stuff only when it's needed. It does that by adding a layer of dependencies just like useEffect (and similar to how React itself knows how something must change in the DOM), but as every single performance optimization technique, it can shoot backwards, if you fill your entire application with unecessary useCallback, useMemo, React.memo the performance will go the other way around.
So the key is to use wisely, carefully choosing what needs to have a stable signature and what doesn't.

Related

Should I wrap this function in a useCallback?

I have a pretty decent understanding of how useCallback works. Figuring out when to use it though seems to be subjective amongst me and my colleagues though. I'm curious what everyone else thinks about our current dilemma.
Imagine we have a component that is dispatching an action to redux as a result of something being selected:
const SelectionComponent = props => {
const dispatch = useDispatch()
const handleSelect = (selection) => {
dispatch(actions.updateSelection(selection))
}
return <Select onSelect={handleSelect} ... />
}
My colleague believes we should wrap handleSelect in a useCallback to make sure the function has a stable identity since it's being passed as a callback to a child component:
const SelectionComponent = props => {
const dispatch = useDispatch()
const handleSelect = useCallback((selection) => {
dispatch(actions.updateSelection(selection))
}, [dispatch])
return <Select onSelect={handleSelect} ... />
}
So my question is, which is the better solution, and why?
Some notes:
useDispatch returns a dispatch function with a stable identity
No rerenders or performance issues occur without the useCallback
EDIT
Just to clarify, this is a question on whether or not we should memoize a function on the basis of maintaining a stable identity when passed to a child component, even if the component tree is not expected to rerender for any reason.
Memoizing handleSelect only makes a difference if Select is memoized as well. Remember - when a component re-renders it also re-renders all of it's children by default (regardless of if their props change or not).
Therefore without knowing how Select is implemented, we can't really say if the useCallback actually has any affect much less if it is "better".
Usually, this type of optimization is unnecessary though. Unless your Select is complex or expensive in some way, you probably do not need to memoize either.
same my question,
I just find the answer https://github.com/reduxjs/react-redux/issues/1468
"dispatch will be the same function reference the entire time. (In fact, in earlier versions of React-Redux, passing a new store reference was forbidden. We do now support changing it at runtime, but realistically you probably won't do that.)
However, the ESLint rule doesn't know that - it just knows that dispatch isn't a built-in React hook return value, so it might change, and therefore it tells you it should be added to the dependencies array just in case it ever does change."

Is the old `setX` value from `useState` still valid after the state is updated? [duplicate]

Is useState's setter able to change during a component life ?
For instance, let's say we've got a useCallback which will update the state.
If the setter is able to change, it must be set as a dependency for the callback since the callback use it.
const [state, setState] = useState(false);
const callback = useCallback(
() => setState(true),
[setState] // <--
);
The setter function won't change during component life.
From Hooks FAQ:
(The identity of the setCount function is guaranteed to be stable so it’s safe to omit.)
The setter function (setState) returned from useState changes on component re-mount, but either way, the callback will get a new instance.
It's a good practice to add state setter in the dependency array ([setState]) when using custom-hooks. For example, useDispatch of react-redux gets new instance on every render, you may get undesired behavior without:
// Custom hook
import { useDispatch } from "react-redux";
export const CounterComponent = ({ value }) => {
// Always new instance
const dispatch = useDispatch();
// Should be in a callback
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch]
);
return (
<div>
<span>{value}</span>
// May render unnecessarily due to the changed reference
<MyIncrementButton onIncrement={dispatch} />
// In callback, all fine
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};
The short answer is, no, the setter of useState() is not able change, and the React docs explicitly guarantee this and even provide examples proving that the setter can be omitted.
I would suggest that you do not add anything to the dependencies list of your useCallback() unless you know its value can change. Just like you wouldn't add any functions imported from modules or module-level functions, constant expressions defined outside the component, etc. adding those things is just superfluous and makes it harder to read your handlers.
All that being said, this is all very specific to the function that is returned by useState() and there is no reason to extend that line of reasoning to every possible custom hook that may return a function. The reason is that the React docs explicitly guarantee the stable behavior of useState() and its setters, but it does not say that the same must be true for any custom hook.
React hooks are still kind of a new and experimental concept and we need to make sure we encourage each other to make them as readable as possible, and more importantly, to understand what they actually do and why. If we don't it will be seen as evidence that hooks are a "bad idea," which will prohibit adoption and wider understanding of them. That would be bad; in my experience they tend to produce much cleaner alternatives to the class-based components that React is usually associated with, not to mention the fact that they can allow organizational techniques that simply aren't possible with classes.

React: useEffect vs useMemo vs useState

I was trying to find a concise answer to this on the web without luck.
Is the following correct regarding the differences between useEffect, useMemo and useState?
Both useState and useMemo will remember a value across renders. The difference is that:
useMemo does not cause a re-render, while useState does
useMemo only runs when its dependencies (if any) have changed, while setSomeState (second array item returned by useState) does not have such a dependency array
Both useMemo and useEffect only runs when their dependencies change (if any). The difference is that:
useEffect runs after a render happens, while useMemo runs before
Any other key differences I have missed?
Your points are basically correct, some minor clarification:
useState is causing a re-render on the call of the setState method (second element in the array returned). It does not have any dependencies like useMemo or useEffect.
useMemo only recalculates a value if the elements in its dependency array change (if there are no dependencies - i.e. the array is empty, it will recalculate only once). If the array is left out, it will recalculate on every render. Calling the function does not cause a re-render. Also it runs during the render of the component and not before.
useEffect is called after each render, if elements in its dependency array have changed or the array is left out. If the array is empty, it will only be run once on the initial mount (and unmount if you return a cleanup function).
You can always check Hooks API Reference, which is a pretty solid documentation in my opinion
The return value of useEffect(callback, [dependency]) is void and It executes after render().
The return value of useMemo(callback, [dependency]) is NOT void but memoized value and It executes DURING render().
useEffect() can give same optimization as of useMemo() under the following circumstances:
The state variable used in the expensive computation (i.e., count1) is the only dependency of the useEffect.
When we don't mind storing the expensively computed value in state variable.
const [count1, setCount1] = useState(0);
const [expensiveValue, setExpensiveValue] = useState(null);
useEffect(() => {
console.log("I am performing expensive computation");
setExpensiveValue(((count1 * 1000) % 12.4) * 51000 - 4000);
}, [count1]);
Only difference is, useEffect() makes the expensively computed value available after render() while useMemo() makes the value available during the render().
Most of the time it does not matter because if that value has been calculated for rendering in the UI, useEffect() and useMemo() both will make the value available before browser finishes painting.
useMemo Used to memoize calculations/values that belong to the component but not necessarily to the component state e.g. validations, methods that rely on component and must return a value;
const validEmail = React.useMemo(() => validateEmail(email), [email])
/* Now use 'validEmail' variable across the component,
whereas 'useEffect' return value is void and only used for unmounting duties,
like unsubscribing from subscription e.g. removeInterval*/
from docs:
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.
useEffect Side effects:
Mutations, subscriptions, timers, logging, and
other side effects
setState's setTimeout's setInterval' ref assignments, API calls, or anything that doesn't perform heavy calculations more belong in here.
Also remember that optimisation comes at a cost, therefore React suggests to use useMemo only when memoization/optimisation is necessary and in other cases rely on useEffect when necessary:
You may rely on useMemo as a performance optimization, not as a semantic guarantee.
In the future, React may choose to “forget” some previously memoized
values and recalculate them on next render, e.g. to free memory for
offscreen components. Write your code so that it still works without
useMemo — and then add it to optimize performance.
[state, setState] = useState() that is to update the given state variable which stays stable during re-renders and calling setState attached to it triggers a re-render. Purpose of this hook is not much relatable to the above two hooks.

Gatsby: Context update causes infinite render loop

I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
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.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.

React hook useEffect dependency array

I trying to wrap my head around the new hooks api of react. Specifically, I'm trying to construct the classic use case that once was the following:
componentDidUpdate(prevProps) {
if (prevProps.foo !== this.props.foo) {
// animate dom elements here...
this.animateSomething(this.ref, this.props.onAnimationComplete);
}
}
Now, I tried to build the same with a function component and useEffect, but can't figure out how to do it. This is what I tried:
useEffect(() => {
animateSomething(ref, props.onAnimationComplete);
}, [props.foo]);
This way, the effect is only called when props.foo changes. And that does work – BUT! It appears to be an anti-pattern since the eslint-plugin-react-hooks marks this as an error. All dependencies that are used inside the effect should be declared in the dependencies array. So that means I would have to do the following:
useEffect(() => {
animateSomething(ref, props.onAnimationComplete);
}, [props.foo, ref, props.onAnimationComplete]);
That does not lead to the linting error BUT it totally defeats the purpose of only calling the effect when props.foo changes. I don't WANT it to be called when the other props or the ref change.
Now, I read something about using useCallback to wrap this. I tried it but didn't get any further.
Can somebody help?
I would recommend writing this as follows:
const previousFooRef = useRef(props.foo);
useEffect(() => {
if (previousFooRef.current !== props.foo) {
animateSomething(ref, props.onAnimationComplete);
previousFooRef.current = props.foo;
}
}, [props.foo, props.onAnimationComplete]);
You can't avoid the complexity of having a condition inside the effect, because without it you will run your animation on mount rather than just when props.foo changes. The condition also allows you to avoid animating when things other than props.foo change.
By including props.onAnimationComplete in the dependencies array, you avoid disabling the lint rule which helps ensure that you don’t introduce future bugs related to missing dependencies.
Here's a working example:
Suppress the linter because it gives you a bad advice. React requires you to pass to the second argument the values which (and only which) changes must trigger an effect fire.
useEffect(() => {
animateSomething(ref, props.onAnimationComplete);
}, [props.foo]); // eslint-disable-line react-hooks/exhaustive-deps
It leads to the same result as the Ryan's solution.
I see no problems with violating this linter rule. In contrast to useCallback and useMemo, it won't lead to errors in common case. The content of the second argument is a high level logic.
You may even want to call an effect when an extraneous value changes:
useEffect(() => {
alert(`Hi ${props.name}, your score is changed`);
}, [props.score]);
Move the values, that must be fresh (not stale) in the callback but mustn't refire the effect, to refs:
const elementRef = useRef(); // Ex `ref` from the question
const animationCompleteRef = useRef();
animationCompleteRef.current = props.onAnimationComplete;
useEffect(() => {
animateSomething(elementRef, animationCompleteRef.current);
}, [props.foo, elementRef, animationCompleteRef]);
It works because useRef return value doesn't change on renders.

Categories