DEV Community

Arsalan Ahmed Yaldram
Arsalan Ahmed Yaldram

Posted on • Edited on

Create Collapsible components using react hooks

React collapsed is a handy library for creating Collapsible components. It is a headless library, meaning rather than exposing fully styled or minimally styled components it gives us a hook which we can use to create our own Collapsible components. The benefit of such libraries is that you don't have to fight with the internal styles of the component. Styling the components is on the consumer of the library while the logic and state is managed by the hook. Reading through the react-collapsed library code, in this tutorial we will -

  • First create a (rigid) Collapsible component.
  • Refactor our component into a (generic) hook.

Please check the working demo here.

Step One - Project setup

Create a react typescript project, from your terminal run and set the project up by completing the simple questionnaire -

npm create vite@latest react-collapse
Enter fullscreen mode Exit fullscreen mode

Get rid of all the default boilerplate code. Create 2 new files namely Collapse.tsx and utils.ts under utils.ts paste the following -

export const noop = () => {}
Enter fullscreen mode Exit fullscreen mode

Step Two - Styles and Props setup

Our Collapsible component will have 2 main elements namely Toggle & Collapsible. A toggle can be a button, div, on clicking the toggle we will expand / collapse the Collapsible body. Under Collapse.tsx paste the following -

import * as React from 'react';

const collapseHeaderStyles: React.CSSProperties = {
  boxSizing: 'border-box',
  border: '2px solid black',
  color: '#212121',
  fontFamily: 'Helvetica',
  padding: '12px',
  fontSize: '16px',
  cursor: 'pointer',
};

const collapseBodyStyles: React.CSSProperties = {
  boxSizing: 'border-box',
  border: '2px solid black',
  borderTop: 'none',
  color: '#212121',
  fontFamily: 'Helvetica',
  padding: '12px',
  fontSize: '16px',
};

export function Collapse() {
  return (
    <div>
      <div style={collapseHeaderStyles}>Toggle</div>
      <div
        style={{
          boxSizing: 'border-box',
        }}
      >
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptate
          dicta, sapiente, praesentium vero sed deleniti ea dolorum hic commodi
          dolores quam itaque unde, architecto alias.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In our App.tsx lets use our collapse component -

import { Collapse } from "./Collapse";

export function App() {
  return <Collapse />;
}
Enter fullscreen mode Exit fullscreen mode

Run the project you should be able to see this UI
collapse-one

Step Three - Component Props

type CollapsibleProps = {
  /** default state of the collapsible */
  isOpened?: boolean;
  /** triggered when the component starts closing  */
  onCollapseStart?: () => void;
  /** triggered after the component is closed */
  onCollapseEnd?: () => void;
  /** triggered when the component starts opening  */
  onExpandStart?: () => void;
  /** triggered after the component is opened */
  onExpandEnd?: () => void;
  /** triggered on open and close */
  onToggle?: () => void;
};

export function Collapse({
  isOpened = false,
  onCollapseStart = noop,
  onCollapseEnd = noop,
  onExpandStart = noop,
  onExpandEnd = noop,
  onToggle = noop,
}: CollapsibleProps) {}
Enter fullscreen mode Exit fullscreen mode
  • isOpened is the default state of the component whether it should be opened or closed when the page first loads.
  • onExpandStart is triggered when the expand animation starts, component is opening.
  • onExpandEnd is triggered when the expand animation ends, component has opened.
  • onCollapseStart is triggered when the collapse animation starts, component is closing.
  • onCollapseEnd is triggered when the collapse animation ends, component has closed.
  • onToggle is triggered after the component opens and closes, (we will remove it later during the hook refactor).

Step Four - Component State

const easing = "cubic-bezier(0.4, 0, 0.2, 1)";

const duration = 266;

const collapsedStyles = {
  display: "none",
  height: "0px",
  overflow: "hidden",
};

export function Collapse({
  isOpened = false,
  onCollapseStart = noop,
  onCollapseEnd = noop,
  onExpandStart = noop,
  onExpandEnd = noop,
  onToggle = noop,
}: CollapsibleProps) {
  const [isExpanded, setExpanded] = React.useState(isOpened);

 const [collapsibleStyles, setStylesRaw] = React.useState<React.CSSProperties>(
    isOpened ? {} : collapsedStyles
  );

  const uniqueId = React.useId();

  const containerRef = React.useRef<HTMLDivElement | null>(null);

  return (
    <div>
      <div
        id={`react-collapsed-toggle-${uniqueId}`}
        aria-controls={`react-collapsed-panel-${uniqueId}`}
        aria-expanded={isExpanded}
        tabIndex={0}
        style={collapseHeaderStyles}
        onClick={() => {
          onToggle();
        }}
      >
        {isExpanded ? "Close" : "Open"}
      </div>
      <div
        ref={containerRef}
        style={{
          boxSizing: "border-box",
          ...collapsibleStyles,
        }}
      >
        <div
          id={`react-collapsed-panel-${uniqueId}`}
          aria-hidden={!isExpanded}
          style={collapseBodyStyles}
        >
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptate
          dicta, sapiente, praesentium vero sed deleniti ea dolorum hic commodi
          dolores quam itaque unde, architecto alias.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • isOpened is the state of the Collapsible opened or closed.
  • collapsibleStyles will keep a track of the styles of the Collapsible body height, transition, etc. we are basically changing the styles when we transition from expand to collapse or vice-versa.
  • Say if we are in closed (isExpanded = false) state the height of the Collapsible body is 0px and when we transition to opened (isExpanded = true) state the height will be the height of the container.
  • Also, we are using a ref, that we have attached to the Collapsible body element, to get its height. Now under utils.ts paste -
export function getElementHeight(
  el: RefObject<HTMLElement> | { current?: { scrollHeight: number } }
): string | number {
  if (!el?.current) {
    return "auto";
  }

  return el.current.scrollHeight;
}

type AnyFunction = (...args: any[]) => unknown;

export const callAll =
  (...fns: AnyFunction[]) =>
  (...args: any[]): void =>
    fns.forEach((fn) => fn && fn(...args));
Enter fullscreen mode Exit fullscreen mode

Run the project from the terminal and check the output -
collapse-two

Now pass isOpened prop as true to your Collapse component under App.tsx and check the output -

export function App() {
  return <Collapse isOpened />;
}
Enter fullscreen mode Exit fullscreen mode

collapse-three

Step Four - Logic

Now our next task is, when we click on the Toggle Element Collapsible Header in our case, the Collapsible Body should open and if it is opened it should close. In Collapse.tsx paste -

import * as React from "react";
import { flushSync } from "react-dom";

import { callAll, getElementHeight, noop } from "./utils";

const collapseHeaderStyles: React.CSSProperties = {
  boxSizing: "border-box",
  border: "2px solid black",
  color: "#212121",
  fontFamily: "Helvetica",
  padding: "12px",
  fontSize: "16px",
  cursor: "pointer",
};

const collapseBodyStyles: React.CSSProperties = {
  boxSizing: "border-box",
  border: "2px solid black",
  borderTop: "none",
  color: "#212121",
  fontFamily: "Helvetica",
  padding: "12px",
  fontSize: "16px",
};

type CollapsibleProps = {
  /** default state of the collapsible */
  isOpened?: boolean;
  /** triggered when the component starts closing  */
  onCollapseStart?: () => void;
  /** triggered after the component is closed */
  onCollapseEnd?: () => void;
  /** triggered when the component starts opening  */
  onExpandStart?: () => void;
  /** triggered after the component is opened */
  onExpandEnd?: () => void;
  /** triggered on open and close */
  onToggle?: () => void;
};

const easing = "cubic-bezier(0.4, 0, 0.2, 1)";

const duration = 266;

const collapsedStyles = {
  display: "none",
  height: "0px",
  overflow: "hidden",
};

export function Collapse({
  isOpened = false,
  onCollapseStart = noop,
  onCollapseEnd = noop,
  onExpandStart = noop,
  onExpandEnd = noop,
  onToggle = noop,
}: CollapsibleProps) {
  const [isExpanded, setExpanded] = React.useState(isOpened);

  const [collapsibleStyles, setStylesRaw] = React.useState<React.CSSProperties>(
    isOpened ? {} : collapsedStyles
  );

  const uniqueId = React.useId();

  const containerRef = React.useRef<HTMLDivElement | null>(null);

  const setStyles = (newStyles: {} | ((oldStyles: {}) => {})) => {
    flushSync(() => {
      setStylesRaw(newStyles);
    });
  };

  const mergeStyles = (newStyles: {}) => {
    setStyles((oldStyles) => ({ ...oldStyles, ...newStyles }));
  };

  function handleTransitionEnd(e: React.TransitionEvent) {
    if (e.target !== containerRef.current || e.propertyName !== "height") {
      return;
    }

    if (isExpanded) {
      const height = getElementHeight(containerRef);

      // If the height at the end of the transition
      // matches the height we're animating to,
      if (height === collapsedStyles.height) {
        setStyles({});
      } else {
        // If the heights don't match, this could be due the height
        // of the content changing mid-transition
        mergeStyles({ height });
      }

      onExpandEnd();
    }

    onCollapseEnd();
  }

  function expandCollapse(nextState: boolean) {
    setExpanded(nextState);
    if (nextState) {
      requestAnimationFrame(() => {
        onExpandStart();
        mergeStyles({
          willChange: "height",
          display: "block",
          overflow: "hidden",
        });

        requestAnimationFrame(() => {
          const height = getElementHeight(containerRef);
          mergeStyles({
            transition: `height ${duration}ms ${easing}`,
            height,
          });
        });
      });
    } else {
      requestAnimationFrame(() => {
        onCollapseStart();
        const height = getElementHeight(containerRef);
        mergeStyles({
          transition: `height ${duration}ms ${easing}`,
          willChange: "height",
          height,
        });

        requestAnimationFrame(() => {
          mergeStyles({
            height: "0px",
            overflow: "hidden",
          });
        });
      });
    }
  }

  return (
    <div>
      <div
        id={`react-collapsed-toggle-${uniqueId}`}
        aria-controls={`react-collapsed-panel-${uniqueId}`}
        aria-expanded={isExpanded}
        tabIndex={0}
        style={collapseHeaderStyles}
        onTransitionEnd={handleTransitionEnd}
        onClick={callAll(() => expandCollapse(!isExpanded), onToggle)}
      >
        {isExpanded ? "Close" : "Open"}
      </div>
      <div
        ref={containerRef}
        style={{
          boxSizing: "border-box",
          ...collapsibleStyles,
        }}
      >
        <div
          id={`react-collapsed-panel-${uniqueId}`}
          aria-hidden={!isExpanded}
          style={collapseBodyStyles}
        >
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptate
          dicta, sapiente, praesentium vero sed deleniti ea dolorum hic commodi
          dolores quam itaque unde, architecto alias.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • On clicking the collapse header, we will call the expandCollpase function and pass in the nextState.
  • If we are opening the component, we transition from height 0px to the height of the element and vice-versa.
  • We use requestAnimationFrame for performing fast and performant animations. For more info please check this video.
  • To know more about flushSync you can check the docs here. Automatic batching in React is indeed an amazing feature. But, there can be situations where we need to prevent this from happening. For that, React provides a method named flushSync() in react-dom that allows us to trigger a re-render for a specific state update.
  • We want to read height of the Collapsible body so we make sure we do all the changes to the DOM before we read the height.
  • Now from the terminal run npm run dev and check the collapsible component.

Step Five: Sync with external store

Many a times, you would like to control the Collapsible component from an outside piece of state. For that to work we will use -

  • useEffect in our Collapse component to sync external state with our Collapse component state. This useEffect should only run after our component has mounted.
  • Pass onToggle prop to our collapse component so that we can sync our internal state with the outside component. Paste the following in the Collapsible.tsx -
 const mounted = React.useRef<boolean>(false);

 React.useEffect(() => {
   if (mounted.current) {
     expandCollapse(isOpened);
   }
   mounted.current = true;
 }, [isOpened]);
Enter fullscreen mode Exit fullscreen mode

Paste the following in App.tsx and check our Collapsible -

import * as React from 'react';
import { Collapse } from './Collapse';

export function App() {
  const [isExpanded, setIsExpanded] = React.useState(false);

  function toggleCollapse() {
    setIsExpanded((prevState) => !prevState);
  }

  return (
    <React.Fragment>
      <button onClick={toggleCollapse}>Toggle</button>
      <Collapse isOpened={isExpanded} onToggle={toggleCollapse} />
    </React.Fragment>
  );
}
Enter fullscreen mode Exit fullscreen mode

Step Six: Turn component into a generic hook

From the component code we can see we have 2 main elements in the Collapse component one the toggle, on clicking it we are calling the expandCollapse function and the other the collapse body on which we attach our ref, get it's height and spread the styles.
So, if we want to create a hook, we need to basically export the config / props for the toggle and collapse body and that is what react collapse does. Along with that we will also export the isExpanded state and expandCollapse function so that we can toggle our collapse component using say an external button. We want to achieve something similar to the following -

import React from 'react'
import useCollapse from 'react-collapsed'

function App() {
  const { getCollapseProps, getToggleProps, isExpanded } = useCollapse()

  return (
    <div>
      <button {...getToggleProps()}>
        {isExpanded ? 'Collapse' : 'Expand'}
      </button>
      <section {...getCollapseProps()}>Collapsed content</section>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode
  • getToggleProps() which we spread on the toggle element like the Button or Collapsible header this function manages the onClick() for the Toggle element, calls the expandCollapse function.
  • getCollapseProps() which we spread on the Collapsible body so that we can animate its height transition it from closed to open, etc.
  • Finally, we have the isExpaned state of our collapsible. Let's create a new file use-collapse.ts and paste -
import {
  useEffect,
  useState,
  CSSProperties,
  MouseEvent,
  useRef,
  TransitionEvent,
} from "react";
import { flushSync } from "react-dom";

import { callAll, getElementHeight, noop } from "./utils";

const easing = "cubic-bezier(0.4, 0, 0.2, 1)";

const duration = 266;

const collapsedStyles = {
  display: "none",
  height: "0px",
  overflow: "hidden",
};

type UseCollapseInput = {
  /** default state of the collapsible */
  isOpened?: boolean;
  /** triggered when the component is closing  */
  onCollapseStart?: () => void;
  /** triggered after the component is closed */
  onCollapseEnd?: () => void;
  /** triggered when the component is opening  */
  onExpandStart?: () => void;
  /** triggered after the component is opened */
  onExpandEnd?: () => void;
  /** triggered on open and close */
  onToggle?: () => void;
};

export function useCollapse({
  isOpened = false,
  onExpandStart = noop,
  onExpandEnd = noop,
  onCollapseStart = noop,
  onCollapseEnd = noop,
}: UseCollapseInput = {}) {
  const [isExpanded, setExpanded] = useState(isOpened);

  const [collapsibleStyles, setStylesRaw] = useState<CSSProperties>(
    isOpened ? {} : collapsedStyles
  );

  const containerRef = useRef<HTMLElement | null>(null);

  const mounted = useRef<boolean>(false);

  useEffect(() => {
    if (mounted.current) {
      expandCollapse(isOpened);
    }
    mounted.current = true;
  }, [isOpened]);

  const setStyles = (newStyles: {} | ((oldStyles: {}) => {})) => {
    flushSync(() => {
      setStylesRaw(newStyles);
    });
  };

  const mergeStyles = (newStyles: {}) => {
    setStyles((oldStyles) => ({ ...oldStyles, ...newStyles }));
  };

  const handleTransitionEnd = (e: React.TransitionEvent): void => {
    if (e.target !== containerRef.current || e.propertyName !== "height") {
      return;
    }

    if (isExpanded) {
      const height = getElementHeight(containerRef);

      // If the height at the end of the transition
      // matches the height we're animating to,
      if (height === collapsibleStyles.height) {
        setStyles({});
      } else {
        // If the heights don't match, this could be due the height of the content changing mid-transition
        mergeStyles({ height });
      }

      onExpandEnd();
    }

    onCollapseEnd();
  };

  function expandCollapse(nextState: boolean) {
    setExpanded(nextState);
    if (nextState) {
      requestAnimationFrame(() => {
        onExpandStart();
        mergeStyles({
          willChange: "height",
          display: "block",
          overflow: "hidden",
        });

        requestAnimationFrame(() => {
          const height = getElementHeight(containerRef);
          mergeStyles({
            transition: `height ${duration}ms ${easing}`,
            height,
          });
        });
      });
    } else {
      requestAnimationFrame(() => {
        onCollapseStart();
        const height = getElementHeight(containerRef);
        mergeStyles({
          transition: `height ${duration}ms ${easing}`,
          willChange: "height",
          height,
        });

        requestAnimationFrame(() => {
          mergeStyles({
            height: "0px",
            overflow: "hidden",
          });
        });
      });
    }
  }

  function getToggleProps({
    disabled = false,
    onClick = noop,
    ...rest
  }: GetTogglePropsInput = {}): GetTogglePropsOutput {
    return {
      type: "button",
      role: "button",
      id: `react-collapsed-toggle`,
      "aria-controls": `react-collapsed-panel`,
      "aria-expanded": isExpanded,
      tabIndex: 0,
      disabled,
      ...rest,
      onClick: disabled
        ? noop
        : callAll(() => expandCollapse(!isExpanded), onClick),
    };
  }

  function getCollapseProps({
    style = {},
    onTransitionEnd = noop,
    refKey = "ref",
    ...rest
  }: GetCollapsePropsInput = {}): GetCollapsePropsOutput {
    return {
      id: `react-collapsed-panel`,
      "aria-hidden": !isExpanded,
      [refKey]: containerRef,
      ...rest,
      onTransitionEnd: callAll(handleTransitionEnd, onTransitionEnd),
      style: {
        boxSizing: "border-box",
        // additional styles passed, e.g. getCollapseProps({style: {}})
        ...style,
        // style overrides from state
        ...collapsibleStyles,
      },
    };
  }

  return {
    getToggleProps,
    getCollapseProps,
    isExpanded,
    toggle: () => expandCollapse(!isExpanded),
  };
}

type ButtonType = "submit" | "reset" | "button";

export interface GetTogglePropsOutput {
  disabled: boolean;
  type: ButtonType;
  role: string;
  id: string;
  "aria-controls": string;
  "aria-expanded": boolean;
  tabIndex: number;
  onClick: (e: MouseEvent) => void;
}

export interface GetTogglePropsInput {
  [key: string]: unknown;
  disabled?: boolean;
  refKey?: string;
  onClick?: (e: MouseEvent) => void;
}

export interface GetCollapsePropsOutput {
  id: string;
  onTransitionEnd: (e: TransitionEvent) => void;
  style: CSSProperties;
  "aria-hidden": boolean;
}

export interface GetCollapsePropsInput {
  [key: string]: unknown;
  style?: CSSProperties;
  refKey?: string;
  onTransitionEnd?: (e: TransitionEvent) => void;
}
Enter fullscreen mode Exit fullscreen mode
  • getToggleProps function we will be spread on the Toggle element. On clicking the element, the Collapsible element should open and close. Therefore, we have to call expandCollapse function in the onClick of the Toggle element, this we handle in the hook.
  • Another thing is that we can also pass an onClick handler to the getToggleProps, if want to execute some more code, eg. log the clicks.
  • Whatever we are handling internally in the hook, an example been the styles of the Collapsible we are also giving an option for the user to pass in additional styles if he wants. You can see the getCollapse function also takes in optional styles.

Now under App.tsx paste the following -

import React from "react";

import { useCollapse } from "./use-collapse";

const collapseHeaderStyles: React.CSSProperties = {
  boxSizing: "border-box",
  border: "2px solid black",
  color: "#212121",
  fontFamily: "Helvetica",
  padding: "12px",
  fontSize: "16px",
  cursor: "pointer",
};

const collapseBodyStyles: React.CSSProperties = {
  boxSizing: "border-box",
  border: "2px solid black",
  borderTop: "none",
  color: "#212121",
  fontFamily: "Helvetica",
  padding: "12px",
  fontSize: "16px",
};

export function App() {
  const { getCollapseProps, getToggleProps, isExpanded } = useCollapse();

  return (
    <div>
      <div style={collapseHeaderStyles} {...getToggleProps({})}>
        {isExpanded ? "Close" : "Open"}
      </div>
      <div {...getCollapseProps()}>
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
          impedit, quibusdam sapiente voluptatum doloremque laudantium, vel
          porro incidunt quidem dolores quis nostrum laboriosam suscipit ut vero
          molestias obcaecati accusantium culpa reiciendis optio. Autem, vero
          ex.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Toggle the Collapsible using an External Button -

export function App() {
  const { getCollapseProps, getToggleProps, isExpanded, toggle } =
    useCollapse();

  return (
    <div>
      <button onClick={toggle}>
        {isExpanded ? "Close Collapsible" : "Open Collapsible"}
      </button>
      <div style={collapseHeaderStyles} {...getToggleProps({})}>
        {isExpanded ? "Close" : "Open"}
      </div>
      <div {...getCollapseProps()}>
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
          impedit, quibusdam sapiente voluptatum doloremque laudantium, vel
          porro incidunt quidem dolores quis nostrum laboriosam suscipit ut vero
          molestias obcaecati accusantium culpa reiciendis optio. Autem, vero
          ex.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Sometimes we might need to control the Collapsible component using an external state altogether for such uses cases -

export function App() {
  const [isExpanded, setIsExpanded] = React.useState(false);

  const { getCollapseProps, getToggleProps } = useCollapse({
    isOpened: isExpanded,
  });

  function toggleCollapsible() {
    setIsExpanded((prevState) => !prevState);
  }

  return (
    <div>
      <button onClick={toggleCollapsible}>
        {isExpanded ? "Close Collapsible" : "Open Collapsible"}
      </button>
      <div
        style={collapseHeaderStyles}
        {...getToggleProps({
          onClick: toggleCollapsible,
        })}
      >
        {isExpanded ? "Close" : "Open"}
      </div>
      <div {...getCollapseProps()}>
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
          impedit, quibusdam sapiente voluptatum doloremque laudantium, vel
          porro incidunt quidem dolores quis nostrum laboriosam suscipit ut vero
          molestias obcaecati accusantium culpa reiciendis optio. Autem, vero
          ex.
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Take a note that we have to pass the onClick function to the getToggleProps. When we click on the Toggle it will change the state of the hook (internal state) to say true using the expandCollapse function this we are managing internally in the hook.
  • But the App component's state is still false. Therefore, we passed an onClick handler to getToggleProps in order to sync the hook's internal state with App component's state.

Nested Collapsible Example -

function Nested() {
  const { isExpanded, getCollapseProps, getToggleProps } = useCollapse({
    isOpened: true,
  });

  return (
    <div>
      <div style={collapseHeaderStyles} {...getToggleProps()}>
        {isExpanded ? "Close" : "Open"}
      </div>
      <div {...getCollapseProps()}>
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
          impedit, quibusdam sapiente voluptatum doloremque laudantium, vel
          porro incidunt quidem dolores quis nostrum laboriosam suscipit ut vero
          molestias obcaecati accusantium culpa reiciendis optio. Autem, vero
          ex.
        </div>
      </div>
    </div>
  );
}

export function App() {
  const { isExpanded, getCollapseProps, getToggleProps } = useCollapse({
    isOpened: true,
  });

  return (
    <div>
      <div style={collapseHeaderStyles} {...getToggleProps()}>
        {isExpanded ? "Close" : "Open"}
      </div>
      <div {...getCollapseProps()}>
        <div style={collapseBodyStyles}>
          Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis
          impedit, quibusdam sapiente voluptatum doloremque laudantium, vel
          porro incidunt quidem dolores quis nostrum laboriosam suscipit ut vero
          molestias obcaecati accusantium culpa reiciendis optio. Autem, vero
          ex.
          <Nested />
        </div>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • The code that we have under handleTransitionEnd inside of the hook, fixes some nested collapsible issues.

Summary

In this tutorial we first created a Collapsible component and then refactored it into a generic hook. Let me know if you have any questions / suggestions. Special shout out to the maintainers and collaborators of react-collapsed library. Until next time PEACE.

Top comments (0)