Check out bidbear.io Amazon Advertising for Humans. Now publicly available 🚀

Conditional Expand/Collapse Content in React

Contents

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:

Example of expand/collapse content

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

CollapseContent.js
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.

collapse-content.css
.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.

Usage
<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!

Amazon Ad Analytics For Humans

Advertising reports automatically saved and displayed beautifully for powerful insights.

bidbear.io
portfolios page sunburst chart