Nested accordion with react js

Viewed 117

guys. I'm learning reactJS and I'm trying to make a nested accordion from scratch, but i can't figure out how to handle the sub-levels.

This is what I've built so far. I have a few questions.

  • How can i handle the usaState hook so it can be used on the different levels of the accordion (as it is now, I'm using it to change the class "active" dynamically, but since all levels use the same hook, all open and close together).
  • Is there a better way than multiple maps?
  • How can i make this code more like a react component (reusable). I feel it is not the right way to build a component.
  • Is it common to use those libraries (bootstrap react, semantic-ui-react, etc) professionally?

Thanks!

My data:

export default [
  {
    id: 1,
    title: 'Smokes',
    children: [
      {
        id: '1A',
        title: 'CT',
        children: [
          {
            id: '',
            title: 'From half wall',
            // children: [],
          },
          {
            id: '',
            title: 'From car',
            // children: [],
          },
          {
            id: '',
            title: 'From  ct Base',
            // children: [],
          },
        ],
      },
      {
        id: '1B',
        title: 'A long',
        children: [
          {
            id: '',
            title: 'From top mid',
            // children: [],
          },
          {
            id: '',
            title: 'From 2nd mid',
            // children: [],
          },
        ],
      },
    ],
  },
  {
    id: 2,
    title: 'Flashes',
    children: [
      {
        id: '2A',
        title: 'Pit',
        children: [
          {
            id: '',
            title: 'From t top mid',
            // children: [],
          },
          {
            id: '',
            title: 'From 2nd mid',
            // children: [],
          },
          {
            id: '',
            title: 'From apps',
            // children: [],
          },
        ],
      },
      {
        id: '2B',
        title: 'Bomb A',
        children: [
          {
            id: '',
            title: 'From banana',
            // children: [],
          },
        ],
      },
    ],
  },
  {
    id: 3,
    title: 'Molotovs',
    children: [
      {
        id: '3A',
        title: 'Patio',
        children: [
          {
            id: '',
            title: 'From t top mid',
            // children: [],
          },
          {
            id: '',
            title: 'From boiler',
            // children: [],
          },
        ],
      },
      {
        id: '3B',
        title: 'Back site A',
        children: [
          {
            id: '',
            title: 'From A long',
            // children: [],
          },
        ],
      },
    ],
  },
];

Accordion.js:

import React from 'react';
import { useState } from 'react';
import accordionData from '../../data/accordion';

function Accordion() {
  const [selected, setselected] = useState('');

  const toggle = () => {
    setselected(selected === '' ? 'active' : '');
  };

  return (
    <section className="accordion-section">
      <div className=" flex-container accordion-wrapper">
        <div className="accordion">
          {accordionData.map((item) => (
            <div className="accordion-items">
              <div className="accordion-title" onClick={() => toggle()}>
                <h2>{item.title}</h2>
                {/* If seleceted render "-", otherwise "+" */}
                <span>{selected === 'active' ? '-' : '+'}</span>
              </div>
              <div>
                {item.children.map((stratItem) => (
                  <div>
                    <div
                      className={`accordion-item destination ${selected}`}
                      onClick={() => toggle()}
                    >
                      {stratItem.title}
                    </div>
                    <div>
                      {stratItem.children.map((originItem) => (
                        <div
                          className={`accordion-item origin ${selected}`}
                          onClick={() => toggle()}
                        >
                          {originItem.title}
                        </div>
                      ))}
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

export default Accordion;

app.css:

.accordion-section {
  background-color: #687980;
}

.accordion-wrapper{
  background-color: #687980;
  color: #F5F2E7;
  height: 100%;
  width: auto;
}

.accordion {
  width: 25%;
  height: auto;
  padding: 15px;
}
.accordion-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 10px;
  cursor: pointer;
  background-color: #2C3333;
}

.accordion-title span {
  font-size: 30px;
  padding-right: 5px;
}

.accordion-items {
  padding: 15px;
  margin: 5px 0;
  font-weight: lighter;
  
}

.accordion-item {
  height: 30px;
  display: flex;
  align-items: center;
  /* border: 1px solid #2C3333; */
  margin: 2px;
  cursor: pointer;
  transition: 0.20s ease;
  background-color: #4f4f53;
}

.accordion-item:hover{
  transform: translate(5px, 5px);
  transform: scale(5px);
  background-color: #cfe3f3;
  font-weight: bold;
  color:#2C3333
}

.destination {
  padding-left: 15px;
  font-weight: 500;
  font-size: 18px;
  max-height: 0;
  overflow: hidden;
  transition: all 0.5s cubic-bezier(0,1,0,1);
}

.destination.active {
  height: auto;
  max-height: 999px;
  transition: all 0.5s cubic-bezier(0,1,0,1);
}

.origin {
  padding-left: 30px;
  font-weight: 300;
  max-height: 0;
  overflow: hidden;
  transition: all 0.5s cubic-bezier(0,1,0,1);
}

.origin.active {
  height: auto;
  max-height: 999;
  transition: all 0.5s cubic-bezier(0,1,0,1);
}


1 Answers

You can achieve it with a simple recursive function that will work for any depth of your accordion. Change the styles as you want. I have created SubLevelComp as you tried to handle the visibility of each sub-level according to a component level state.

Try like this:

const accordionData = [ { id: 1, title: "Smokes", children: [ { id: "1A", title: "CT", children: [ { id: "", title: "From half wall" }, { id: "", title: "From car" }, { id: "", title: "From ct Base" } ] }, { id: "1B", title: "A long", children: [ { id: "", title: "From top mid" }, { id: "", title: "From 2nd mid" } ] } ] }, { id: 2, title: "Flashes", children: [ { id: "2A", title: "Pit", children: [ { id: "", title: "From t top mid" }, { id: "", title: "From 2nd mid" }, { id: "", title: "From apps" } ] }, { id: "2B", title: "Bomb A", children: [ { id: "", title: "From banana" } ] } ] }, { id: 3, title: "Molotovs", children: [ { id: "3A", title: "Patio", children: [ { id: "", title: "From t top mid" }, { id: "", title: "From boiler" } ] }, { id: "3B", title: "Back site A", children: [ { id: "", title: "From A long" } ] } ] } ];

const SubLevelComp = ({ item, renderNestedLevels }) => {
  const [selected, setselected] = React.useState("");

  const toggle = () => {
    setselected(selected === "" ? "active" : "");
  };

  const hasChidlren = (item) => {
    return Array.isArray(item.children) && item.children.length > 0;
  };

  return (
    <div>
      <p
        onClick={() => toggle()}
        style={{ cursor: hasChidlren(item) ? "pointer" : "" }}
      >
        {item.title}{" "}
        {hasChidlren(item) && <span>{selected === "active" ? "-" : "+"}</span>}
      </p>
      {selected && (
        <div style={{ marginLeft: "20px" }}>
          {hasChidlren(item) && renderNestedLevels(item.children)}
        </div>
      )}
    </div>
  );
};

function Accordion() {
  const renderNestedLevels = (data) => {
    return data.map((item, itemIndex) => (
      <SubLevelComp item={item} renderNestedLevels={renderNestedLevels} key={itemIndex}/>
    ));
  };

  return renderNestedLevels(accordionData);
}

ReactDOM.render(<Accordion />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

Related