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.
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.
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.
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;
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
.
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.
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 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
.
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!
Top comments (0)