You can only pass some variables as parameters for the case1 function outside of the component. See the below example:
import React, { useCallback } from 'react';
const Button = React.memo<{ onClick: any; children: any }>(({ onClick, children }) => {
console.log('render child');
return <button onClick={onClick}>{children}</button>;
});
const case1 = (dep) => {
console.log('expensive work in case1, dep: ', dep);
};
export default function App({ dep }) {
const case3 = useCallback(() => {
console.log('expensive work in case3, dep: ', dep);
}, [dep]);
const case1Bound = case1.bind(this, dep);
return (
<div>
{/* use function declared outside of a component */}
{/* <Button onClick={() => case1(dep)}>click me</Button> */}
<Button onClick={case1Bound}>click me</Button>
{/* useCallback */}
{/* <Button onClick={case3}>click me</Button> */}
</div>
);
}
Suppose the case1 function depends on the dep props and is triggered by the click event of the Button component. You can only pass the dep as a parameter through the arrow function or the .bind() method. As a result, it creates a new function for the onClick prop every time the App component renders. It will cause the React.memo() shallow comparison return false, the Button component will re-render. See below test:
import 'jest';
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import App from './index';
describe('useCallback-vs-function-outside-component', () => {
test('should pass', () => {
const { rerender } = render(<App dep={1} />);
const button = screen.getByText(/click me/);
fireEvent.click(button);
rerender(<App dep={1} />);
fireEvent.click(button);
});
});
Logs:
render child
expensive work in case1, dep: 1
render child
expensive work in case1, dep: 1
With useCallback:
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
So the dep is NOT changed, the case3 function is the function created for the first time, and useCallback will return directly from memory instead of creating a new one. The Button onClick prop is NOT changed, the shallow comparison of React.memo() will return true, so it will render only once.
Logs:
render child
expensive work in case3, dep: 1
expensive work in case3, dep: 1