I have a simple custom hook that returns some functions:
const useCustom = () => {
const someReduxValue = useSelector(...);
const fn1 = useCallback(() => {
// do something with someReduxValue
}, [...]);
const fn2 = useCallback(() => {
// may include some complex operations like reducing/mapping etc
}, [...]);
return useMemo(() => ({
fn1,
fn2,
}), [...]);
};
My question: is there any advantage in wrapping each functions (fn1, fn2) into useCallbacks and then wrapping all what hook returns into useMemo?
Note: no state used in this hook.
It totally depends on your function.
useMemo and useCallback must be used carefully Because it fills the memory!
useMemo Returns a memoized value.
For example:
you have a function for checking that number is prime or not?
this function examin number 9851812358!
this is a big number for checking that and suppose in every rerender this function check this huge number for prime number!!!
useMemo memoized this value (is prime or not) and in re-rendering just returns last result and doesn't calculate this number again and again.
useMemo memoized value but useCallback returns a memoized callback!
Reactjs Docs:
will return a memoized version of the callback that only changes if
one of the dependencies has changed. This is useful when passing
callbacks to optimized child components that rely on reference
equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
now. you should decide your function needs useMemo and useCallback or not!
Related
I use this pattern sometimes where I declare a curried function inside useCallback.
const Child = ({ handleClick }) => {
return (
<>
<button onClick={handleClick("foo")}>foo</button>
<button onClick={handleClick("lorem")}>lorem</button>
</>
);
};
export default function App() {
const [state, setState] = useState("");
const handleClick = useCallback(
(newState) => () => {
setState(newState);
},
[]
);
return (
<div className="App">
<Child handleClick={handleClick} />
<p>{state}</p>
</div>
);
}
because I want to pass arguments from JSX to the event handlers and to avoid multiple handlers.
When the component rerenders, handleClick will be called and the function that is returned will be assigned to the onClick prop, but will it be a new function every time or will the nested function also get memoized by useCallback?
PS: This is a simple example. Assume a useCallback usage with multiple dependencies
Edit
Following answer refers to the initial version of the OP's code in which handleClick was not passed as a prop to Child component.
You don't really need useCallback in this case.
Actually, it would be better not to use the useCallback hook in this case because:
Callback function passed to useCallback is created everytime your component re-renders. As a result, a new function is getting created anyways, just like it would without the use of useCallback
Getting a memoized version of the handleClick function doesn't provides any benefit in your case. Memoization would be useful if handleClick is passed as a prop to child component(s).
...but will it be a new function every time or will the nested
function also get memoized by useCallback?
No, the nested function won't be memoized.
handleClick is a memoized function but that memoized function returns a new function everytime it executes.
Invoking handleClick is just like invoking any other function - anything declared inside its body is created everytime it is invoked.
It should, either curry or not, it's a function instance, as long as you want to keep an old function instance, you can use it.
But don't use it because the prop onClick, because when your component renders, this button has to render regardless of the onClick. So your line can be simply as
const onClick = value => () => { setState(value) }
Or you can even promote this to a global function.
const handle = setState => value => () => { setState(value) }
NOTE: i don't want to say useCallback most of time is useless. But actually if you don't know why you want to use a useMemo, don't use a useCallback. They are designed to skip an assignment, but not designed to skip a render, or in your case, to improve reusability. (not for that purpose).
For ever more optimisation / common logic, I want to make a good useSelectors that uses multiple useCallback.
Here useSeletors is to make selectors (like in redux) on useReducer.
So this is my code :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector =>
(...args) => (selector(state, ...args))
),
[state]
);
What I wanted to do is :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector => useCallback(
(...args) => (selector(state, ...args))),
[state]
),
[state]
);
Which is actually causing an error.
I probably can do this :
const useSelectors = (state, selectors) => useMemo(
() => selectors.map(selector => selector(state)),
[state]
);
But I lose the possibility to use a arguments to my selector function.
Maybe it isn't a problem, because there is something that I don't understand yet, about useCallback :
If I use a useCallback without giving arguments but in giving a dependecy, it will almost be like a variable.
At the first time I call it, is it executed ? Next times (without updating the dependency), I directly get the return of the function without execution ?
So what would happen if I put variable arguments (a counter for example) in the callback ?
Will it probably re-executed the function with this new argument even if the dependency doesn't change ?
So did the useCallback structure become useless with arguments ?
The last point, that I want to ask, is : when a callback is executed?
For a useMemo, the function is executed the first time at is declaration ? (Or the first time we use its variable ? => The difference is not really important for this case.)
Did useCallback is called a first time only when it is called with () in the code or at its declaration ?
Example :
const myVar = useMemo(() => 5+5, [dependency]); // executed
const myFunc = useCallback(() => 5+5, [dependency]);
myVar; // 10
myFunc(); // executed => 10
myVar; // 10
myFunc(); // 10
So if it works like this it's better to call useCallBack in the useMemo, to execute the selector only when it is call and not at the mount in my third solution.
This is the main reason why I want to use multiple useCallbacks in my hook, without knowing the number.
[EDIT]
Second proposition with useCallback has no sens because :
const test = useCallback(() => {
console.log('test is executed');
return 'test';
}, [state]);
console.log(test());
console.log(test());
Logs :
test is executed
test
test is executed
test
And not that I expected :
test is executed
test
test
So it couldn't do better than the first
useCallback returns a function. When one of the dependency of that hook (for example, some counter) is changed useCallback invoked again and return new function. It does because the function passed to useCallback doesn't know that the counter is changed until useCallback re-executes it again.
useMemo works the same but it returns a value. In theory you can implement useCallback using useMemo, returning function instead of value. I mean useCallback(fn, deps) is the same as useMemo(() => fn, deps).
You can read more about it in the React docs.
In your code you should use useMemo and don't use useCallback inside useMemo because you'll get an error and it doesn't unnecessary because it uses when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders. It isn't your case. Your first example of code is correct.
I know that useCallback recreates a function when its dependencies change, so it is some kind of wrapper for memoizing functions, useful for accessing the most updated state in useEffects callbacks (for example).
My question here is simple. Is there any difference for accessing the most freshed state value between using useCallback(() => {}, [carData]) and setCarData(prevCarData => console.log(`Most freshed state: ${JSON.stringify(prevCarData)}`);
I mean, can I get into troubles with the second way? Or the only difference is the memoization of the function?
UPDATE
A)
const memoizedFunc = useCallback(() => {
...
setCarData({...carData, maxVelocity: 50});
}, [carData]);
useEffect(() => {
if (!carData.maxVelocity) {
memoizedFunc();
}
}, [..., memoizedFunc]);
B)
const func = () => {
setCarData((prevCarData) => ({...prevCarData, maxVelocity: 50}));
};
useEffect(() => {
if (!carData.maxVelocity) {
func();
}
}, [...]);
Thank you.
I mean, can I get into troubles with the second way?
No. You're much more likely to get into trouble with A (using useCallback for a function you later use in useEffect). You're not at all likely to get into trouble with B (using functional updates).
Beware that useCallback (like useMemo) doesn't guarantee that it won't recreate the function if the dependencies don't change. It's a performance optimization, not a semantic guarantee. Your combination of useCallback and useEffect may cause the effect function to run even when nothing other than the callback has changed, and changed simply because React decided to forget the old version. See the warning on useMemo:
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.
(their emphasis)
This applies to useCallback too, because as they say at the very end of the useCallback documentation:
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
Using the first approach you will be creating a different callback every time carData changes, which in turn rerenders any hook/component that you pass the callback as a prop.
A better approach would be to utilize setCarData implicit prevState to avoid such unnecessary rerender.
I am finding myself in a weird situation. I am implementing an hook and I cannot manage to achieve what I want.
I have something like this:
const appHook = props => {
const [foo, setFoo] = React.useState([]);
const [bar, setBar] = React.useState([]);
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
setBar(getBar(foo.listToFilter));
}, [props.fooId]);
const getCurrentBlockTrade = (arrayToFilter, number) =>
arrayToFilter.filter(array => array.id === number);
const getSubOutList = (...) => {
...
};
return (<div>something</div>)
}
My issue is that the function setFoo is properly executed, so foo state is a new array, but setBar that depends on the state of foo, receives an empty array. Basically setBar is executed before setFoo finished so the getBar function receives an empty array.
What is the right way to manage this kind of dependency?
Thanks,
F.
TL;DR; Your solution is likely kind user's answer
Below I'll describe what I've thought and learned so far throughout researches, and come up with 5 suggestions/solutions from people, via blogs,...
You've said:
My issue is that the function setFoo is properly executed, so foo state is a new array, but setBar that depends on the state of foo, receives an empty array. Basically setBar is executed before setFoo finished so the getBar function receives an empty array.
You're true. Basically because in React (both Hooks and class component), setState is asynchronous. What does it mean? It means that setSomething just tells React to re-render the component later. It doesn't magically replace the const something variable in the current running function — that's not possible.
const [foo, setFoo] = useState(0)
function handleClick() {
setFoo(42)
// we declared foo with const, you "obviously" shouldn't expect this
// to "somehow" immediately change `foo` to 42
console.log(foo);
// it's 0 in this render, BUT on next render, `foo` will be 42
}
Solution 1.
The easiest technique to you is to store the newly calculated value of foo in a variable, then use that newly calculated value to both setFoo and setBar - a quite popular technique tho.
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
// or: shouldn't use this, only to demonstrate the callback syntax in
// the new setState Hook (different than the old callback syntax setState):
React.useEffect(() => {
setFoo(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setBar(getBar(newFoo.listToFilter));
return newFoo;
})
}, [props.fooId]);
Solution 2.
Another technique can be found here: https://stackoverflow.com/a/54120692/9787887 is using useEffect to setBar with the dependency list whose foo.
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
Despite the answer get 27 upvotes, I think it's just overcomplicated the situation, and also (as I know) make the component unnecessarily rerender 2 times instead of 1, should be avoided.
Solution 3.
Another solution that might work is to use async/await to make the state changes triggered asynchronously, to lead the changes not be batched (regarding this answer https://stackoverflow.com/a/53048903/9787887)
React.useEffect(async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}, [props.fooId]);
// no, actually this will not work!! it'll throw you an (annoyed) error
// the actual working code is:
React.useEffect(() =>
const setFooAndBar = async () => {
await setFoo(getFoo(props.fooList, props.fooId));
await setBar(getBar(foo.listToFilter));
}
setFooAndBar();
}, [props.fooId]);
You see, the working code is again another overcomplicated (and bad) solution, (but should be introduced anyway??).
Solution 4.
Another solution that gaearon mentioned is to use useReducer
With Hooks you could also useReducer to centralize state update logic and avoid this pitfall.
Another his insight:
the recommended solution is to either use one variable instead of two (since one can be calculated from the other one, it seems), or to calculate the next value first and update them both using it together. Or, if you're ready to make the jump, useReducer helps avoid these pitfalls.
But it again seems to be another overcomplex suggestion to this case, doesn't it?
Solution 5.
The last suggestion is a comment of gaearon, tell you to rethink about your state dependence, is the state dependence really needed?
the best solution is simply to not have state that is calculated from another state. If this.state.y is always calculated from this.state.x, remove this.state.y completely, and only track this.state.x. And calculate what you need when rendering instead
Thank you for being patient enough to read to here :)).
Setting a state is an asynchronus process. So setBar(getBar(foo.listToFilter)); calling this foo is the empty array. You can use another useEffect for this
React.useEffect(() => {
setFoo(getFoo(props.fooList, props.fooId));
}, [props.fooId]);
React.useEffect(() => {
setBar(getBar(foo.listToFilter));
}, [foo]);
setState is an asynchronous function, that's why you are receiving an empty array in setBar function. Basically you can't be sure that the state will be updated before the second setState evaluates.
Why not to simply refer to the props in both cases?
React.useEffect(() => {
const newFoo = getFoo(props.fooList, props.fooId);
setFoo(newFoo);
setBar(getBar(newFoo.listToFilter));
}, [props.fooId]);
Are there any benefits in using useMemo (e.g. for an intensive function call) instead of using a combination of useEffect and useState?
Here are two custom hooks that work exactly the same on first sight, besides useMemo's return value being null on the first render:
useEffect & useState
import { expensiveCalculation } from "foo";
function useCalculate(someNumber: number): number | null {
const [result, setResult] = useState<number | null>(null);
useEffect(() => {
setResult(expensiveCalculation(someNumber));
}, [someNumber]);
return result;
}
useMemo
import { expensiveCalculation } from "foo";
function useCalculateWithMemo(someNumber: number): number {
return useMemo(() => {
return expensiveCalculation(someNumber);
}, [someNumber]);
};
Both calculate the result each time their parameter someNumber changes, where is the memoization of useMemo kicking in?
The useEffect and setState will cause extra renders on every change: the first render will "lag behind" with stale data and then it'll immediately queue up an additional render with the new data.
Suppose we have:
// Maybe I'm running this on a literal potato
function expensiveCalculation(x) { return x + 1; };
Lets suppose x is initially 0:
The useMemo version immediately renders 1.
The useEffect version renders null, then after the component renders the effect runs, changes the state, and queues up a new render with 1.
Then if we change x to 2:
The useMemo runs and 3 is rendered.
The useEffect version runs, and renders 1 again, then the effect triggers and the component reruns with the correct value of 3.
In terms of how often expensiveCalculation runs, the two have identical behavior, but the useEffect version is causing twice as much rendering which is bad for performance for other reasons.
Plus, the useMemo version is just cleaner and more readable, IMO. It doesn't introduce unnecessary mutable state and has fewer moving parts.
So you're better off just using useMemo here.
I think there are two main points you should consider when choosing between them.
Time when function called.
useEffect called after component has been rendered, so you can access DOM from it. For example, this is important if you want to access DOM elements via refs.
Semantic guarantees.
useEffect guarantees that it will not be fired if dependencies have not changed. useMemo does not give such guarantees.
As stated in the React documentation, you should consider useMemo as pure optimization technique. Your program should continue to work correctly even if you replace useMemo with regular function call.
useEffect + useState can be used to control updates. Even to break-up circular dependencies and prevent infinite update loops.
I would say other than the async nature, there might be some difference in terms how they are designed.
useEffect is a collective call, async or not, it's collected after all components are rendered.
useMemo is a local call, which has only something to do with this component. You could just think of useMemo as another assignment statement with benefits to use the assignment from last update.
This means, useMemo is more urgent, and then useLayoutEffect and the last being useEffect.