memo and useCallback() to the rescue
Tags: react
Published: 2021-02-28 07:35:31

Since I use Angular at my day job and I really want to practice React, I thought of creating a simple application that will allow me to get my hands dirty. For this app, I needed a vertical resizer that will change the width sizes of the panels that are adjacent to it.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqbcp6u9pqcdaf8ymnok.png)
 

First, I implemented a `Resizer` component that renders a small vertical bar that users can click and drag left or right. This `Resizer` component listens to mouse events to capture the user's mouse movements.

```tsx
import React, { useEffect } from 'react';

const Resizer = ({ onResize }: { onResize: (pageX: number) => void }) => {
  let dragging = false;

  const dragStart = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    event.preventDefault();
    dragging = true;
  };

  const dragMove = (event: MouseEvent) => {
    if (dragging) {
      onResize(event.pageX);
    }
  };

  const dragEnd = (event: MouseEvent) => {
    dragging = false;
  };

  useEffect(() => {
    window.addEventListener('mousemove', dragMove);
    window.addEventListener('mouseup', dragEnd);

    return () => {
      window.removeEventListener('mousemove', dragMove);
      window.removeEventListener('mouseup', dragEnd);
    };
  });

  return <div className="resizer" onMouseDown={dragStart}></div>;
};

export default Resizer;
```

I then added an `onResize` event handler in the parent container to listen to the resize event emitted by the `Resizer` component. The handler just logs the new width received from the `Resizer` component to the console.

```tsx
const App = () => {

  const onResize = (resizedWidth: number) => {
    console.log(resizedWidth);
    setNewWidth(resizedWidth);
  };

  return (
    <div>
      <div className="left-panel">
        LEFT
      </div>
      <Resizer onResize={onResize}></Resizer>
      <div className="right-panel">
        RIGHT
      </div>
    </div>
  );
};

export default App;
```

It worked perfectly.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jszw5jcj47btzdkd89sq.png)

So the next step was to adjust the width of the left panel when the `onResize` event gets emitted. To do this, I added `newWidth` state to the parent container and set the left panel's width to the value held by `newWidth`.

```tsx
const App = () => {
  const [newWidth, setNewWidth] = useState(300);

  const onResize = (resizedWidth: number) => {
    console.log(resizedWidth);
    setNewWidth(resizedWidth);
  };

  return (
    <div>
      <div className="left-panel" style={{ width: newWidth }}>
        LEFT
      </div>
      <Resizer onResize={onResize}></Resizer>
      <div className="right-panel">
        RIGHT
      </div>
    </div>
  );
};

export default App;
```

But when I tried this out, the panels were not resizing and the developer console is only logging a single value.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/814etrylj7neudx72keq.png)

UH-OH! :(
 
After looking into this a bit further, I figured out that the `Resizer` component gets re-rendered when the state changes in the parent container (i.e., when `setNewWidth(resizedWidth);` is called). 

To fix this, I need to somehow make the `Resizer` not dependent on the parent container's state. Luckily, React has an API for this--the [React.memo](https://reactjs.org/docs/react-api.html#reactmemo) API. According to the documentation, `React.memo` is a higher order component that only checks for prop changes.

To make this work, I have to make sure that the props passed to the `Resizer` component does not change. To do this, I have to wrap the `onResize` event handler (props passed to `Resizer`) with [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback).

```tsx
const MemoizedResizer = memo<typeof Resizer>(({ onResize }) => (
  <Resizer onResize={onResize}></Resizer>
));

const App = () => {
  const [newWidth, setNewWidth] = useState(300);

  const onResize = useCallback((resizedWidth: number) => {
    console.log(resizedWidth);
    setNewWidth(resizedWidth);
  }, []);

  return (
    <div>
      <div className="left-panel" style={{ width: newWidth }}>
        LEFT
      </div>
      <MemoizedResizer onResize={onResize}></MemoizedResizer>
      <div className="right-panel">
        RIGHT
      </div>
    </div>
  );
};

export default App;
```

After application of the said fixes... VOILA!

![resizer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3dypmcarzs72odcs11da.gif)