← Back toDrag and drop in React

Resize columns in a table

Written byPhuoc Nguyen
Created
07 Nov, 2023
Last updated
10 Nov, 2023
Properly sizing columns in a table is essential for making the information displayed easy to read and understand. When columns are not adjusted correctly, the data can be cramped or too spread out, leading to confusion. This can even result in important data being cut off, which is not ideal when making important decisions.
Thankfully, resizing columns is a simple process that can be done by dragging the column border. Hover your mouse over the line that separates two columns until it turns into a double-sided arrow, and then click and drag to adjust the width of the column. This method allows for precise control over the column size.
By taking the time to resize columns properly, you can ensure that your data is presented in an organized and clear manner, making it easier to analyze. In this post, we'll learn how to resize columns by dragging the border using React.

Adding a resizer handler to each column header

If you want to resize columns in a table, you need to add a resizer handler. To do this, we simply insert a `div` element inside the `th` element of each column header:
tsx
<th className="column">
Column header
<div className="resizer" />
</th>
We're going to use the `div` element with the class name `resizer` to resize our columns when they're clicked and dragged.
To put the resizer on the right side of each column header, we'll set the `position` property of the resizer to `absolute`. This lets us position the resizer relative to its nearest positioned ancestor element (in this case, the column header). We'll set the `top` and `right` properties to 0 so that the resizer lines up with the right edge of the column header. And we'll make it 100% tall so it fills up the whole parent element, and 0.25rem wide so it's visible but not too distracting. With these styles in place, our resizer is ready to go!
css
.column {
position: relative;
}

.resizer {
cursor: col-resize;

position: absolute;
top: 0;
right: 0;

height: 100%;
width: 0.25rem;
}
Let's talk about how to make the resizer even better. By default, the resizer has a transparent background, but we can improve user experience by changing that.
To make it more obvious when users hover over the resizer, we'll add another rule that sets the `background` property of the `.resizer:hover` class to a different shade of blue.
With these simple style changes, the resizer will have a subtle yet effective visual effect that can greatly enhance usability and user experience.
css
.resizer {
background: transparent;
}
.resizer:hover {
background: rgb(99 102 241);
}
Here's a preview of the column headers and their resizers. Please note that the resizing functionality is not yet available.

Adjusting column width

To adjust the width of a column when users drag the resizer handler, we'll use the same approach we outlined in the previous post for making an element resizable.
As a reminder, we used the `useRef()` hook to create a reference to the target element, which we then attached to the element using the `ref` attribute. Additionally, we handled the `mousedown` and `touchstart` events to make the target element draggable on both desktop and touchscreen devices.
Here's a quick code snippet to refresh your memory:
tsx
const resizerRef = React.useRef();

// Render
<div
className="resizer"
ref={resizerRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
In the past, we used to position draggable elements by setting their position within their container and updating their position with the `transform` property. However, we've made some changes for our resizable feature. We no longer rely on internal states like `dx` and `dy`. Instead, we now store the starting point and current dimensions of the element when users start dragging the resizer.
To achieve this, we've made some adjustments to how we handle the `mousedown` event. Here's what we've done:
ts
const handleMouseDown = (e) => {
const ele = resizerRef.current;
const startPos = {
x: e.clientX,
y: e.clientY,
};

const parent = ele.parentElement;
const styles = window.getComputedStyle(parent);
const w = parseInt(styles.width, 10);
const h = parseInt(styles.height, 10);
// ...
};
When a user clicks on a resizer element, we use the `handleMouseDown` function to capture the mouse coordinates with `e.clientX` and `e.clientY`. Then, we calculate the current width and height of the resizable element using `window.getComputedStyle(parent)`, where `parent` is the column header.
To access the column header, you can retrieve it by accessing the `parentElement` property of the resizer element.
As the user moves the mouse, we calculate the horizontal and vertical distance the mouse moves. We then update the dimension of the column header accordingly.
ts
const handleMouseMove = (e) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
parent.style.width = `${w + dx}`;
};

Resizing tables with dynamic number of columns

The approach we discussed above works perfectly for a single column table. However, in reality, tables can have a dynamic number of columns. So how can we create a resizer and handle its events for each column?
Since it's not possible to create a ref using `useRef()` for each column, we'll use a pattern of passing a ref to a child component. This pattern is outlined in detail in this post.
To encapsulate the logic, we can create a new component called `Resizable`. This component will handle the resizing of each column and ensure that the table remains responsive and user-friendly.
tsx
const Resizable = ({ children }) => {
const [node, setNode] = React.useState<HTMLElement>(null);

const ref = React.useCallback((nodeEle) => {
setNode(nodeEle);
}, []);

const handleMouseDown = React.useCallback((e: React.MouseEvent) => {
// ...
}, [node]);

const handleTouchStart = React.useCallback((e: React.TouchEvent) => {
// ...
}, [node]);

React.useEffect(() => {
if (!node) {
return;
}
node.addEventListener("mousedown", handleMouseDown);
node.addEventListener("touchstart", handleTouchStart);

return () => {
node.removeEventListener("mousedown", handleMouseDown);
node.removeEventListener("touchstart", handleTouchStart);
};
}, [node]);

return children({ ref });
};
The `Resizable` component is a handy tool that lets you resize columns in a table. It's reusable and easy to implement. All you need to do is pass in a single child function that will be called with an object containing a reference to the resizable element.
To ensure the reference remains stable across renders, we use the `useCallback` hook. Then, when the `Resizable` component mounts, it attaches event listeners for both mouse down and touch start events to its underlying element. This allows us to call `handleMouseDown` and `handleTouchStart` when the respective events are triggered, passing along the event object.
Using the `Resizable` component to render columns is a breeze. Simply create a column header for each column and pass it to the `Resizable` component as its child function. The child function receives an object containing a reference to the resizable element, which we attach to the resizer `div` inside the column header.
tsx
<tr>
{
["No.", "First name", "Last name"].map((title) => (
<Resizable key={title}>
{
({ ref }) => (
<th className="column">
{title}
<div className="resizer" ref={ref} />
</th>
)
}
</Resizable>
))
}
</tr>
In this example, we're creating a table with three columns labeled "No.", "First name", and "Last name". To achieve this, we loop through each title using `Array.map()` and create a new `Resizable` component for each one. Inside the `Resizable` component, we pass a child function that receives an object with a `ref` property. This ref is then used to attach it to the resizer `div` inside the column header.
With this approach, we can easily add or remove columns from our table without having to worry about attaching event listeners or handling resizing logic ourselves. The `Resizable` component takes care of all of that for us!
Check out the final demo below. Hover your mouse over the border of a column header, and drag the resizer handler to adjust the width of the corresponding column. It's that simple!

Conclusion

In conclusion, using React to resize columns of a table is a smart way to enhance the user experience and usability of your web application. By creating a resizable component that handles mouse and touch events, you can offer users an easy way to adjust column widths according to their preferences.
Our approach, outlined in this post, uses the `useRef()` hook to create a reference to the resizable element. This reference is then attached to the resizer `div` inside each column header. We also handle mouse down and touch start events to make the target element draggable on both desktop and touchscreen devices.
To make our component reusable for a dynamic number of columns, we encapsulated our logic within a new component called `Resizable`. This component takes in a single child function that receives an object containing a `ref` property, which we use to attach it to the resizer `div` inside each column header.
Overall, implementing resizable columns using React is a straightforward process that can significantly improve the usability of your web application. With just a few lines of code, you can provide users with an intuitive way to customize their view and enhance their overall experience.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn