useMemo vs. useEffect + useState - javascript

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.

Related

The point of using useCallback and useMemo in custom hooks

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!

React - Updating data in dispatch from useEffect

I am trying updating data in dispatch in useEffect but showing warning in console
React Hook useEffect has missing dependencies: 'dispatch', 'id', and 'state.selectedHotel'. Either include them or remove the dependency array react-hooks/exhaustive-deps
code
import { GlobalContext } from "../../../context/globalContext";
const HotelDetail = () => {
const [state, dispatch] = useContext(GlobalContext);
const { id } = useParams();
useEffect(() => {
const hotelData = async () => {
try {
let response = await ServiceGetAllHotels();
let hotel = response.hotels.filter(hotel => {
return hotel.hotelUserName === id;
});
dispatch({
type: "UPDATE",
payload: { selectedHotel: hotel[0] }
});
}catch(){}
};
}, [])
};
But warning message disappear when I add this (below code)
useEffect(() => {
.....
}, [dispatch, state.selectedHotel, id])
I dont understand why this error/warning , why error disappear when I add this ? Please help Can I go with this code?
Its not an error but a warning that can save you from bugs because of useEffect hook not running when it was supposed to.
useEffect hook, by default, executes after:
the initial render
each time a component is re-rendered
Sometimes we don't want this default behavior; passing a second optional argument to useEffect hook changes the default execution behavior of useEffect hook. Second argument to useEffect hook is known as its dependency array that tells React when to execute the useEffect hook.
Run "useEffect" once, after the initial render
We can achieve this by passing an empty array as the second argument to the useEffect hook:
useEffect(() => {
// code
}, []);
This effect will only execute once, similar to componentDidMount in class components.
Run "useEffect" everytime any of its dependency changes
When the code inside the useEffect depends on the state or a prop, you sometimes want useEffect to execute every time that piece of state or prop changes.
How can we tell React to run the effect every time a particular state or prop changes? By adding that state or prop in the dependency array of the useEffect hook.
Example:
Imagine a Post component that receives post id as a prop and it fetches the comments related to that post.
You might write the following code to fetch the comments:
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, []);
Problem with the above code:
When the Post component is rendered for the first time, useEffect hook will execute, fetching the comments using the id of the post passed in as the argument.
But what if the post id changes or the post id is not available during the first render of the Post component?
If post id prop changes, Post component will re-render BUT the post comments will not be fetched because useEffect hook will only execute once, after the initial render.
How can you solve this problem?
By adding post id prop in the dependency array of the useEffect hook.
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, [props.postId]);
Now every time post id changes, useEffect will be executed, fetching the comments related to the post.
This is the kind of problem you can run into by missing the dependencies of the useEffect hook and React is warning you about it.
You should not omit any dependencies of the useEffect hook or other hooks like: useMemo or useCallback. Not omitting them will save you from such warnings from React but more importantly, it will save you from bugs.
Infinite loop of state update and re-render
One thing to keep in mind when adding dependencies in the dependency array of the useEffect is that if your are not careful, your code can get stuck in an infinite cycle of:
useEffect --> state update ---> re-render --> useEffect ....
Consider the following example:
useEffect(() => {
const newState = state.map(...);
setState(data);
}, [state, setState]);
In the above example, if we remove the state from the dependency array, we will get a warning about missing dependencies and if we add state in the array, we will get an infinite cycle of state update and re-render.
What can we do?
One way is to skip the state as a dependency of the useState hook and disable the warning using the following:
// eslint-disable-next-line react-hooks/exhaustive-deps
Above solution will work but it's not ideal.
Ideal solution is to change your code in such a way that allows you to remove the dependency that is causing the problem. In this case, we can simply use the functional form of the setState which takes a callback function as shown below:
useEffect(() => {
setState(currState => currState.map(...));
}, [setState]);
Now we don't need to add state in the dependency array - problem solved!
Summary
Don't omit the dependencies of the useEffect hook
Be mindful of the infinite cycle of state update and re-render. If you face this problem, try to change your code in such a way that you can safely remove the dependency that is causing the infinite cycle
The useEffect hook accepts two arguments. The first one is a classic callback and the second one is an array of so called "dependencies".
The hook is designed to execute the callback immediately after component has been mount (after elements have been successfully added to the real DOM and references are available) and then on every render if at least one of the values in the dependencies array has changed.
So, if you pass an empty array, your callback will be executed only once during the full lifecycle of your component.
It makes sense if you think about it from a memory point of view. Each time that the component function is executed, a new callback is created storing references to the current execution context variables. If those variables change and a new callback is not created, then the old callback would still use the old values.
This is why "missing dependencies" is marked as a warning (not an error), code could perfectly work with missing dependencies, sometimes it could be also intentional. Even if you can always add all dependencies and then perform internal checks. It is a good practice to pass all your dependencies so your callback is always up to date.

How to get the correct state in an useEffect hook, after another useEffect hook modifies the same state?

I have two useEffect hooks, which will both run on change in startTime/endTime state.
useEffect(() => {
if (selectedProduct) {
fetch(...)
.then(res => res.json())
.then(res => setMyState(res.data));
}
}, [selectedProduct, startTime, endTime]);
useEffect(() => {
if(selectedStep){
console.log(myState); //this runs twice, once for the old state and once for the new state, after above useEffect's setState is complete
}
}, [selectedStep, startTime, endTime, myState]);
I understand from looking around that this is probably because React does some sort of batching for performance gain such that setState is async in nature. Also, the fetch call would be async too, so it will take time to set the new state.
From what I have searched, I see that there is a callback for class based React components using setState but as I am using hooks, I am not sure what to do. I can move the second useEffect code in the first one, but that would require some ugly if-else conditions as I need to check if selectedStep has not changed, and then I can use the res.data directly.
Is there a better way of doing this?
It's not batching of operations or anything like that, it's just that that's how it works: When startTime or endTime changes, each of those hooks is executed, and since the first changes myState, your second one gets called again for that change.
A few options for you
Combine the hooks as you described, even if it involves conditional logic.
Depending on what you're doing with myState, you might make the second hook use the callback form of setMyState which receives the current state value as a parameter.
(If possible) Remove the dependencies on other state items in your second useEffect so that it only gets executed when myState changes
Despite your saying it'll involve some if/else, I'd lean toward #1 if you can't do #3.

React - Effect of closures within React application

I have stumbled upon an issue that is very weird for me, but most probably is very simple to explain.
Demo
Let's assume the following React component
import React, { useState, useEffect, useCallback } from "react";
export default function App() {
const [test, setTest] = useState();
const doSomething = () => {
// TODO: Why does this returns the inital state value? Hoisting?
console.log(test);
};
const doSomethingWithCallback = useCallback(doSomething, [test]);
useEffect(() => {
setTest("asas");
window.setTimeout(() => doSomething(), 2000);
document.addEventListener("click", doSomethingWithCallback);
return () => {
document.removeEventListener("click", doSomethingWithCallback);
};
}, [doSomethingWithCallback]);
return (
<div className="App">
<h1>Click anywhere</h1>
</div>
);
}
(cf. CodeSandbox)
Question
Look at the TODO comment in the code.
Why does doSomething console logs the state test as it is initially is set, namely undefined whereas the callback variant is returning it with the "real" current state when it is called?
Is this some kind of hoisting or performance optimization React is doing?
This doesn't have much of anything to do with hoisting. setTest is causing a rerender which will cause the useCallback to evaluate it's callback since it's dependency changed.
Explaining why the other function call returns the initial value is a little more mysterious, but I think it has to do with the value of test when you registered the call to setTimeout.
Any setState is asynchronous, you can not guarantee the value in state will line up with what you expect if you set the state and then synchronously try to reference state right afterwards. To the contrary, it most likely won't, I'm not fully informed, but I believe more or less no asynchronous work is done until the queue of synchronous work is completely empty.
As for your actual question, "are state values hoisted?" yes they are, like all values in Javascript. However react state hook values differ slightly in that the order of their execution must always be the same between renders, so you have to maintain at least that much (i.e. no conditional hook instantiation may exist).

React - useCallback vs useState's setState(prevState... difference for accessing previous state

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.

Categories