One of the key difference is order of execution of useEffect and useMemo.
useEffect is run after the component is rendered.
useMemo is run before the component is rendered.
Why is this important?
See below usecase where we are adding Salutation to a username
1. using useEffect -
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const getSaluation = (name) => `Hi, ${name}`;
const App = () => {
const [name, setName] = useState('');
const [fullName, setFullName] = useState('');
const renderCount = useRef(0);
renderCount.current = renderCount.current+1;
useEffect(() => {
setFullName(() => getSaluation(name))
}, [name])
return(<>
<input type="text" value={name} onChange={((e) => setName(e.target.value))}></input>
Name - {fullName}
{console.log('render order ', renderCount.current) /* here component is twice everytime the name variable is changed by the user */}
</>
)
}
// plus un above code we need to unnecessary create fullName state variable
// component is renderd twice everytime
// reason - name variable changed, component re-renders, after rerender, useEffect is called, there we are setting fullName, again render method is called
// so every time 2X re-renders
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<!-- end snippet -->
2. using useMemo
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
const getSaluation = (name) => `Hi, ${name}`
const App = () => {
const [name, setName] = useState('');
const renderCount = useRef(0);
renderCount.current = renderCount.current+1;
const fullName = useMemo(() => getSaluation(name),[name]);
return (
return (
<> <input type="text" value={name} onChange={((e) => setName(e.target.value))}></input>
Name - {fullName}
<Button onClick={() => setCount(count + 1)}>
{console.log('render order ', renderCount.current)}
<span role="img" aria-label="react-emoji">⚛️</span>
</Button>
</>
)
)
}
// we eliminated useEffect + now we don;t need fullName state variable as well/
// because when name state variable is changed, component re-renders,
// and everytime fullName variable is calculated from useMemo
// and since useMemo is run before the render method is called
// we have latest value of fullName and component renders only
<!-- language: lang-html -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<!-- end snippet -->
You are calling the memoized callback every time, when you do:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
This is why the count of `useCallback` is going up. However the function never changes, it never *****creates**** a new callback, its always the same. Meaning `useCallback` is correctly doing it's job.
Let's making some changes in your code to see this is true. Let's create a global variable, `lastComputedCallback`, that will keep track of if a new (different) function is returned. If a new function is returned, that means `useCallback` just "executed again". So when it executes again we will call `expensiveCalc('useCallback')`, as this is how you are counting if `useCallback` did work. I do this in the code below, and it is now clear that `useCallback` is memoizing as expected.
If you want to see `useCallback` re-create the function everytime, then uncomment the line in the array that passes `second`. You will see it re-create the function.
<!-- begin snippet: js hide: false console: true babel: true -->
<!-- language: lang-js -->
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
let lastComputedCallback;
function App() {
const [second, setSecond] = useState(0);
// This 👇 is not expensive, and it will execute every render, this is fine, creating a function every render is about as cheap as setting a variable to true every render.
const computedCallback = useCallback(() => expensiveCalc('useCallback'), [
neverChange,
// second // uncomment this to make it return a new callback every second
]);
if (computedCallback !== lastComputedCallback) {
lastComputedCallback = computedCallback
// This 👇 executes everytime computedCallback is changed. Running this callback is expensive, that is true.
computedCallback();
}
// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${expensiveCalcExecutedTimes.useCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < 10000) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<!-- language: lang-html -->
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<!-- end snippet -->
Benefit of `useCallback` is that the function returned is the same, so react is not `removeEventListener`'ing and `addEventListener`ing on the element everytime, UNLESS the `computedCallback` changes. And the `computedCallback` only changes when the variables change. Thus react will only `addEventListener` once.
Great question, I learned a lot by answering it.