How to Build an Accordion Menu in React from Scratch – No External Libraries Required

Accordion menu example

An accordion menu is a common user interface pattern that allows content to be collapsed and expanded vertically. It‘s useful for organizing and displaying large amounts of information in a compact format, like FAQs, product details, or form sections.

While there are plenty of ready-made accordion components available for React, building one from scratch is a great way to sharpen your skills and deepen your understanding of the framework. In this step-by-step guide, we‘ll walk through creating a versatile accordion menu in React without relying on any external libraries.

Setting up the Project

Let‘s start by spinning up a new React project using create-react-app. Open a terminal and run:

npx create-react-app react-accordion
cd react-accordion

Once the project has been created, start the development server:

npm start

With the initial setup in place, we can begin building out our accordion component.

Creating the Accordion Component

We‘ll start with a basic Accordion component that renders a single collapsible section. Create a new file called Accordion.js in the src directory:

import React, { useState } from ‘react‘;

function Accordion() {
  const [isOpen, setIsOpen] = useState(false);

  const toggle = () => setIsOpen(!isOpen);

  return (
    <div className="accordion">
      <button className="accordion-header" onClick={toggle}>
        <h3>Section Title</h3>
        <span>{isOpen ? ‘-‘ : ‘+‘}</span>
      </button>
      {isOpen && (
        <div className="accordion-content">
          <p>Section content goes here...</p>
        </div>
      )}
    </div>
  );
}

export default Accordion;

Here‘s a breakdown of what‘s happening:

  • We import the useState hook for managing the open/closed state of the accordion section
  • The isOpen state variable keeps track of whether the section is currently expanded
  • The toggle function inverts the isOpen state when called
  • In the returned JSX:
    • The header contains the section title and a + or – icon to indicate if it‘s collapsed or expanded
    • An onClick handler on the header toggles the section when clicked
    • The content is conditionally rendered based on the isOpen state using the && operator
    • Some placeholder text is shown when the section is expanded

Next, add some basic styles in App.css to make the accordion more visually appealing:

.accordion {
  border: 1px solid #ccc;
  border-radius: 4px;
  margin-bottom: 10px;
}

.accordion-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  background: #f7f7f7;
  border: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
}

.accordion-header h3 {
  margin: 0;
}

.accordion-content {
  padding: 10px;
}

Finally, import the Accordion component into App.js and render it:

import Accordion from ‘./Accordion‘;

function App() {
  return (
    <div className="app">

      <Accordion />
    </div>
  );
}

export default App;

And with that, we have a basic working accordion! Clicking the header will toggle the section content. Now let‘s take it a step further by adding support for multiple sections.

Supporting Multiple Accordion Sections

To make our Accordion component more flexible and reusable, we‘ll refactor it to accept the section title and content as props. This will allow us to render multiple instances of the component with different data.

Update Accordion.js as follows:

function Accordion({ title, content }) {
  const [isOpen, setIsOpen] = useState(false);

  const toggle = () => setIsOpen(!isOpen);

  return (
    <div className="accordion">
      <button className="accordion-header" onClick={toggle}>
        <h3>{title}</h3>
        <span>{isOpen ? ‘-‘ : ‘+‘}</span>
      </button>
      {isOpen && (
        <div className="accordion-content">
          <p>{content}</p>
        </div>
      )}
    </div>
  );
}

Now the component expects title and content props instead of hardcoding the text.

Next, define an array of section data that will be passed to each Accordion:

// App.js
const sections = [
  {
    title: ‘Section 1‘,
    content: ‘Lorem ipsum dolor sit amet, consectetur adipiscing elit.‘
  },
  {
    title: ‘Section 2‘, 
    content: ‘Suspendisse id velit in risus aliquam imperdiet.‘
  },
  {
    title: ‘Section 3‘,
    content: ‘Phasellus ornare justo sed purus pharetra dapibus.‘
  }
];

Finally, render multiple Accordions in App.js by mapping over the sections array:

function App() {
  return (
    <div className="app">

      {sections.map((section, index) => (
        <Accordion
          key={index}
          title={section.title}
          content={section.content}
        />
      ))}
    </div>
  );
}

Now we have a fully functioning multi-section accordion menu! Each Accordion maintains its own internal isOpen state, so sections can be toggled independently.

Animating the Accordion

To add a touch of polish, let‘s animate the opening and closing of the accordion sections using CSS transitions. We‘ll target the height property for a smooth sliding effect.

First, add a content-wrapper div inside the Accordion to better control the animation:

{isOpen && (
  <div className="accordion-content">
    <div className="content-wrapper">
      <p>{content}</p>
    </div>
  </div>
)}

Then update the CSS:

.accordion-content {
  overflow: hidden;
  transition: height 0.3s ease;
}

.content-wrapper {
  padding: 10px;
}

The overflow: hidden on the content div ensures that the content is clipped when collapsed, and the transition property specifies which CSS property to animate (height), the duration (0.3s), and the easing function (ease).

For an extra touch, let‘s rotate the +/- icon when a section is expanded. Add a new CSS class to control this:

<span className={`toggle-icon ${isOpen ? ‘open‘ : ‘‘}`}>
  {isOpen ? ‘-‘ : ‘+‘}
</span>
.toggle-icon {
  transition: transform 0.3s;
}

.toggle-icon.open {
  transform: rotate(45deg);
}

Now the icon will smoothly rotate when a section is toggled!

Optimizing Performance

As it stands, our accordion is perfectly functional. However, there are a couple optimizations we can make to improve performance, especially as the number of sections grows.

First, consider lazy loading the content of collapsed sections. There‘s no need to render the content of a section until it‘s expanded. We can use React‘s lazy and Suspense to dynamically import content components:

// Section1Content.js
function Section1Content() {
  return <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>;
}

export default Section1Content;
// Accordion.js
import React, { lazy, Suspense, useState } from ‘react‘;

function Accordion({ title, contentComponent: ContentComponent }) {
  const [isOpen, setIsOpen] = useState(false);

  const toggle = () => setIsOpen(!isOpen);

  return (
    <div className="accordion">
      <button className="accordion-header" onClick={toggle}>
        <h3>{title}</h3>
        <span className={`toggle-icon ${isOpen ? ‘open‘ : ‘‘}`}>
          {isOpen ? ‘-‘ : ‘+‘}
        </span>
      </button>
      {isOpen && (
        <div className="accordion-content">
          <Suspense fallback={<div>Loading...</div>}>
            <ContentComponent />
          </Suspense>
        </div>
      )}
    </div>
  );
}
// App.js
import Section1Content from ‘./Section1Content‘;

const sections = [
  {
    title: ‘Section 1‘,
    contentComponent: lazy(() => import(‘./Section1Content‘))
  },
  // ...
];

Now the content components will only be loaded when needed.

Another potential optimization is memoizing the Accordion component to avoid unnecessary re-renders when the section data hasn‘t changed. React‘s memo higher-order component can handle this:

const Accordion = memo(function Accordion({ title, contentComponent }) {
  // ...
});

Conclusion

And there you have it! We‘ve built a versatile accordion menu in React from the ground up. The component supports multiple independently toggled sections, smooth animations, and is optimized for performance.

Some additional features you might consider adding:

  • Allowing multiple sections to be expanded at once
  • Persisting the open/closed state of sections across page reloads using localStorage
  • Integrating the accordion with a CMS or database to populate the section content dynamically

I hope this step-by-step guide has helped solidify your understanding of React‘s core concepts and given you the confidence to tackle UI components without reaching for external libraries. The complete code for this tutorial can be found on GitHub.

Now go forth and build amazing things with React!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *