In the previous sample we saw how to make a component pure using React.memo, that's great, but when there's an issue what happens if we pass a function created inside the functional component to the child component? That function will be always different on every render thus the memo won't take effect.
How can we solve this? We can make use of useCallback, this won't mutate the setter function unless we indicate any dependency (same approach as with React.useEffect).
- We will take as starting point sample 08-pure-component. Copy the content of the project to a fresh folder an execute npm install.
npm install
- Let's open the demo.tsx file. We will create a parent and a child component (this time the child component will just reset the name content).
./src/demo.tsx
import React from "react";
interface Props {
onReset: () => void;
}
const ResetValue: React.FC<Props> = React.memo((props) => {
console.log(
"Hey I'm only rendered the first time, check React.memo + callback"
);
return <button onClick={props.onReset}>Reset value</button>;
});
export const MyComponent = () => {
const [username, setUsername] = React.useState("John");
const [lastname, setLastname] = React.useState("Doe");
const resetNameCallback = () => {
setUsername("");
};
return (
<>
<h3>
{username} {lastname}
</h3>
<input value={username} onChange={(e) => setUsername(e.target.value)} />
<input value={lastname} onChange={(e) => setLastname(e.target.value)} />
<ResetValue onReset={resetNameCallback}>Reset name</ResetValue>
</>
);
};
-
If we run the sample we will check that the render is always triggered (resetNameCallback is always recreated, shallow compare will fail).
-
The trick here is to use React.useCallback and passing as a second argument an empty array (it will just hold the reference for the function forever).
import React from "react";
export const MyComponent = () => {
const [username, setUsername] = React.useState("John");
const [lastname, setLastname] = React.useState("Doe");
- const resetNameCallback = () => {setUsername('');}
+ const resetNameCallback = React.useCallback(() => setUsername(''), []);
return (
<>
<h3>
{username} {lastname}
</h3>
<input value={username} onChange={e => setUsername(e.target.value)} />
<input value={lastname} onChange={e => setLastname(e.target.value)} />
<ResetValue onReset={resetNameCallback}>Reset name</ResetValue>
</>
);
};
const ResetValue = React.memo(props => {
console.log(
"Hey I'm only rendered the first time, check React.memo + callback"
);
return (
<button onClick={props.onReset}>Reset value</button>
);
});
- If we run he example, we can see that the rerender is no longer launched in the component.
How does this work? useCallback will return a function that was originally created, and it returns this instead of creating a new one in each render, we achive this by passing an empty array as a second argument(as we did with React.useEffect) and if we omit the second parameter, this function will be reevaluated after each render.
We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
Basefactor, consultancy by Lemoncode provides consultancy and coaching services.
Lemoncode provides training services.
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend