Conditional Expand/Collapse Content in React
Intro
Let's say that I'm building a comments section for an application, one of the common things we may want to do when displaying those comments is offer the ability to expand and contract the comments so they don't take up too much room on the page, like this:
However, what would be really nice, is if we could detect the height of the comments section and then only offer the expand/collapse functionality if the comments section exceeds a certain height. If there is only one comment, then there is no need to offer the expand/collapse functionality.
I went ahead and built a component that does just that. It is used as a wrapper around the content that you want to expand/collapse. It detects the height of the content inside it and then offers the ability to expand or collapse the content if it exceeds a certain height. That height "breakpoint" is configurable using a prop on the component.
The Component
import { useState, useRef, useEffect } from "react";
import ArrowDownSmall from "./icons/ArrowDownSmall";
import ArrowUpSmall from "./icons/ArrowUpSmall";
const CollapseContent = ({ children, CollapseHeight }) => {
let [contentHeight, setContentHeight] = useState(null);
let [isOpen, setIsOpen] = useState(false);
const collapseContainerRef = useRef(null);
//console.log("contentHeight", contentHeight);
useEffect(() => {
if (collapseContainerRef.current) {
const children = collapseContainerRef.current.children;
//console.log("children", children);
// measure the height of the first child
setContentHeight(children[0].getBoundingClientRect().height);
}
}, [children]); // Empty
const renderContent = () => {
// only collapse if content is greater than X pixels
if (contentHeight > CollapseHeight) {
if (!isOpen) {
return (
<div className="content-collapsed" ref={collapseContainerRef}>
{children}
<div className="collapsed-overlay">
<button
className="button standard mb-1"
onClick={() => {
setIsOpen(true);
}}
>
<ArrowDownSmall Color="black" Class="icon-small" />
Show All
</button>
</div>
</div>
);
} else {
return (
<div ref={collapseContainerRef}>
{children}
<div className="flex-column-center">
<button
className="button standard mt-1"
onClick={() => {
setIsOpen(false);
}}
>
<ArrowUpSmall Color="black" Class="icon-small" />
Collapse
</button>
</div>
</div>
);
}
} else {
return <div ref={collapseContainerRef}>{children}</div>;
}
};
return <>{renderContent()}</>;
};
export default CollapseContent;
and then here are the relevant styles for the collapse classes.
.content-collapsed {
/* flex-column-center */
display: flex;
flex-direction: column;
align-items: center;
width: auto;
max-height: 400px;
overflow-y: clip;
position: relative;
}
.collapsed-overlay {
/* center contents */
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: center;
z-index: 2;
position: absolute;
bottom: 0;
height: 140px;
width: 100%;
background: linear-gradient(to top, white, white 30%, transparent);
border-bottom: 1px solid var(--border-color);
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
}
The only tricky part really is getting that linear gradient background to be positioned correctly.
Usage
Usage is very straightforward. You wrap the content you want to collapse in the component and then pass in the height at which you want the content to collapse.
<CollapseContent CollapseHeight={600}>{renderComments()}</CollapseContent>
If the child components exceed the given height, then the ability to expand or collapse the content will be offered. Otherwise it will just be displayed normally. Very handy!
Comments
Recent Work
Basalt
basalt.softwareFree desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.
BidBear
bidbear.ioBidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.