function prop as dependency for useCallback - javascript

I am not really sure when we should avoid using useCallback, if there is any harm (memory reallocation). For example lets say I have a component with two props, {onSave, onClose} and one sate viewName. is bellow handler function will be optimized with these dependencies?
const handleSaveView = useCallback(() => {
onSaveView(viewName, selectedViewList);
onClose();
}, [onSaveView, onClose, viewName]);

useCallback saves a function you pass to it and, in the future, returns that function instead of the new one if any of the values in the dependency array change.
This comes at a cost, mostly in the tests of the dependency array and, most of the time, it isn't worth using. It's a very tempting tool for premature optimization.
There are times when that cost is worth paying, such as when the function has an internal state or it is a dependency of another hook (so recreating the function would trigger the other hook to re-run).
Dmitri Pavlutin's "Your Guide to React.useCallback()" covers this in more depth.

Related

Cannot Read Property of undefined Object in dependencies array in useEffect or useCallback in React

I have a useCallback() method below to improve the performance. This will be same logic with useEffect()
If I have a dependency which is router.asPath, but sometimes the router is null, which may cause the function crash.
In order to improvement performance, I do not want to put the whole object of router, as other fields changes, I do not want rerun the function.
Any suggest?
const recordProduct = useCallback(() => {
dispatch(setNextCollectionScroll(router.asPath, hit.handle))
}, [dispatch, hit.handle, router])
The ideal dependency: [dispatch, hit.handle, router.asPath]
But I have to do it now: due to the router object may be null: [dispatch, hit.handle, router]
It is not clear what router are you using, and if you should look elsewhere, but in your simple case of checking dependencies in both useEffect and useCallback you can use Optional chaining from JS.
const recordProduct = useCallback(() => {
dispatch(setNextCollectionScroll(router.asPath, hit.handle))
}, [dispatch, hit.handle, router?.asPath])
When router is null, ? will skip trying to get asPath and you will not get a runtime error.
SIDENOTE
In my opinion you should always use a specific property in an object that you are depending on, especially if you are using a third-party library, as it may happen that they return same object reference with a different value (this should be labeled as badly written library, but just in case depend on properties).
It is easier to follow the flow of data and why a particular dependency hook fires.

Should you pass setter functions into the dependency array of a React hook?

Recently I saw a few examples of passing setter functions into hook dependency arrays in my coworkers' React code, and it doesn't look right to me. For example:
const MyComponent = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
doSomeBigLongNetworkRequest();
setLoading(false);
}, [setLoading, /* other deps */]);
// ...
}
My feeling is that they have misunderstood the purpose of the dependency array, which, as I understand it, is to indicate which pieces of state to monitor so that the hook can fire again when they change, not to simply indicate that the hook needs to use the setLoading function. And since the setLoading function never actually changes, including it in the dependencies does nothing.
Am I correct, or does including the setter in the array make sense somehow? My other thought was that maybe this was just a linter error, since the linter cannot recognize that the function is a setter, and thinks it might change.
I should also add that in the instances I've seen, they've included the setter but not the variable. So in the example above, setLoading, but not loading would be in the dependency array, and the hook does not actually need the value of loading.
Yes, you are right there is no need to include them. Here is quote from docs:
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
In general again based on docs the recommendation about dependency array is:
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.

How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?

I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.

React hooks callback ref pattern

I often face this situation with react callbacks:
const MyComponent = ({ onLoad }) => {
useEffect => {
// some stuff
onLoad(/* args */);
}, [onLoad]);
return (<div />);
}
The problem is. I consider my component should only load one time. And with useEffect, i have to set onLoad in the dependencies, this cause any change to the onLoad prop to trigger the effect.
I generally solve this issue with a ref
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
It works well and solve a lot of similar problems, but i find it a bit ugly, and not really beginner-friendly. I wonder if there is better solutions, or if what i do is an anti-patern ?
As from the comments above: This is a good resource for how to use useEffect
https://reacttraining.com/blog/useEffect-is-not-the-new-componentDidMount/
This article specifically highlights the main reasons on why you need to think of useEffect differently from the Class Component lifecycle methods.
We often times do some setup when the component first mounts like a
network call or a subscription. We have taught ourselves to think in
terms of "moments in time" with things like componentDidMount(),
componentDidUpdate(), and componentWillUnmount(). It's natural to take
that prior knowledge of React and to seek 1:1 equivalents in hooks. I
did it myself and I think everyone does at first. Often times I'll
hear in my workshops...
"What is the hooks equivalent to [some lifecycle method]?"
The quick answer is that hooks are a paradigm shift from thinking in
terms of "lifecycles and time" to thinking in terms of "state and
synchronization with DOM". Trying to take the old paradigm and apply
it to hooks just doesn't work out very well and can hold you back.
It also gives a good run through of the useEffect and an example of converting from a Class Component to hooks.
Another good source is https://overreacted.io/a-complete-guide-to-useeffect/ from Dan Abramov. I definitely recommend this even though it's very long to read. It really helped me when I first got started using hooks to think about them the right way.
Here's a small excerpt from the beginning of the article.
But sometimes when you useEffect, the pieces don’t quite fit together.
You have a nagging feeling that you’re missing something. It seems
similar to class lifecycles… but is it really? You find yourself
asking questions like:
🤔 How do I replicate componentDidMount with useEffect?
🤔 How do I correctly fetch data inside useEffect? What is []?
🤔 Do I need to specify functions as effect dependencies or not?
🤔 Why do I sometimes get an infinite refetching loop?
🤔 Why do I sometimes get an old state or prop value inside my effect?
When I just started using Hooks, I was confused by all of those
questions too. Even when writing the initial docs, I didn’t have a
firm grasp on some of the subtleties. I’ve since had a few “aha”
moments that I want to share with you. This deep dive will make the
answers to these questions look obvious to you.
To see the answers, we need to take a step back. The goal of this
article isn’t to give you a list of bullet point recipes. It’s to help
you truly “grok” useEffect. There won’t be much to learn. In fact,
we’ll spend most of our time unlearning.
It’s only after I stopped looking at the useEffect Hook through the
prism of the familiar class lifecycle methods that everything came
together for me.
In terms of the original question above, using refs is a good way to be able to not have your effect have specific functions and values as dependencies.
In particular they are good if you "you want to read the latest rather than captured value inside some callback defined in an effect"
For this example from the poster:
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
This is a completely valid way of doing things, though depending on the args that onLoad takes, and how it works, it might be a good idea to add extra items to the dependency array to make it always in sync.
You could abstract away the wonkiness of the useRef here, but unfortunately the rules of hooks eslint plugin wouldn't recognize it as a ref. It would work, you'd just need to add the onLoadRef to the dependency array, though it would never cause the effect to re-run. It's similar to things like dispatch from react-redux where you know it is stable, but the eslint plugin can't know that.
function useRefUpdater(value) {
const ref = useRef(value);
// I forget where I saw that you should change the ref in a useEffect
useEffect(() => {
ref.current = value;
}, [value]);
return ref;
}
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRefUpdater(_onLoad)
useEffect(() => {
// some stuff
onLoadRef.current(/* args */);
}, []);
// React Hook useEffect has a missing dependency: 'onLoadRef'. Either include it or remove the dependency array.
return <div />;
};

React: Optimization callbacks with or without useCallback

TL;DR
Some blogs say that you don't need to use the useCallback hook every time you pass a callback to child component, and sometimes it is better to create a new function every time. Because the cost of useCallback sometimes higher than the actual performance issue. While React warns that it may lead to performance issues and we need to try to avoid it.
Who is right and what is the way to balance between these two opposing claims?
The full question
I've been reading a lot of blogs and tutorials about react hooks recently, specially about useCallback and useMemo.
Before React 16 (and all its hooks) when I use Class Components, I've always used "constructor binding" for my callbacks, because arrow function or "Bind in render" like those:
render() {
return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
}
render() {
return <button onClick={() => this.handleClick()}>Click Me</button>;
}
Were considered as "bad practice". From React Docs:
Using Function.prototype.bind in render creates a new function each time the component renders, which may have performance implications (see below).
Using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
So, my rule of thumb was not to pass new functions to props.
In React 16 the useCallback hook tries to helps us to do exactly that thing. But I see a lot of blogs like that or that claim that you don't need to use the useCallback hook every time because the cost of useCallback sometimes higher than the actual issue, and using arrow functions that create a new function on every render is fine.
Both opposing claims made me think that the best option should be something like (if the dependencies array is empty):
function handleCallback() { ... } // Or arrow function...
function Foo(props) {
return <button onClick={handleCallback}>Click Me</button>
}
Because this way doesn't use the "expensive" useCallback and doesn't generating a new function every time. But, actually, this code is not looking so good when you have several callbacks.
So, what is the right balance between these two opposing claims? If creating a new function every time sometimes better than using useCallback, why React has a warning in their docs about it?
(Same question about the "double curly braces" rule of thumb and useMemo/useRef hooks)

Categories

Resources