As far as I understand, React.memo is an API that memoize a component: if its props don't change, react use the latest render of that component without comparing it with its previous version. Skipping this comparing speed-up the app. Cool.
Now, here's what I don't get: if props don't change, also a not memoized component don't get re-rendered, as I can see from that simple code: there's no difference about number of renders between a normal component+usecallback and a memoized one+useCallback. Basically, useCallbacks is all I need, as a normal component doesn't get re-rendered with same props. Then, what I'm I missing? When memo comes in help to optimize?
import { useCallback, useEffect, useState, memo, useRef } from "react";
import "./styles.css";
function Child({ fn, txt }) {
const [state, setState] = useState(0);
console.log(txt + " rendered!");
useEffect(() => {
setState((state) => state + 1);
}, [fn]);
return (
<div style={{ border: "solid" }}>
I'm a Child
{!fn && <div>And I got no prop</div>}
{fn && <div>And I got a fn as a prop</div>}
<div>and I've got rendered {state} times</div>
</div>
);
}
const MemoChild = memo(({ fn, txt }) => {
const [state, setState] = useState(0);
const counter = useRef(0);
console.log(txt + " rendered!", counter.current);
useEffect(() => {
setState((state) => state + 1);
counter.current++;
}, [fn]);
return (
<div style={{ border: "solid" }}>
I'm a Child
{!fn && <div>And I got no prop</div>}
{fn && <div>And I got a fn as a prop</div>}
<div>and I've got rendered {state} times</div>
</div>
);
});
export default function App() {
const [, setState] = useState(true);
const handlerOfWhoKnows = () => {};
return (
<div className="App">
I'm the parent
<br />
<button onClick={() => setState((state) => !state)}>
Change parent state
</button>
<h3>Ref</h3>
ref: <Child txt="ref" fn={handlerOfWhoKnows} />
<h3>useCallback test</h3>
useCB: <Child txt="useCb" fn={useCallback(handlerOfWhoKnows, [])} />
memo: <Child txt="memo" fn={handlerOfWhoKnows} />
memo + useCB:{" "}
<MemoChild txt="memo+useCb" fn={useCallback(handlerOfWhoKnows, [])} />
</div>
);
}
Your example is misleading you a bit because you never change the state of App.
Now, here's what I don't get: if props don't change, also a not memoized component don't get re-rendered...
That's incorrect. If the parent re-renders, an unmemoized child will re-render, even if its props don't change.
When memo comes in help to optimize?
When you don't want that to happen. :-)
Here's an example, notice that Unmemoized re-renders as the counter goes up , but Memoized does not; neither of them uses the counter, so neither of them needs to re-render, but the unmemoized one does anyway because that's the default behavior:
const { useState, useEffect } = React;
const Unmemoized = ({ value }) => {
console.log(`Unmemoized rendering with "${value}"`);
return <div>Unmemoized: {value}</div>;
};
const Memoized = React.memo(({ value }) => {
console.log(`Memoized rendering with "${value}"`);
return <div>Memoized: {value}</div>;
});
const Example = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCounter(c => {
++c;
if (c === 5) {
clearInterval(timer);
}
return c;
});
}, 800);
return () => {
clearInterval(timer);
};
}, []);
return (
<div>
<div>Counter: {counter}</div>
<Memoized value="a" />
<Unmemoized value="b" />
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>