Ale
Ale

Reputation: 93

Nested accordion with react js

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.

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);
}


Upvotes: 1

Views: 4463

Answers (1)

Amila Senadheera
Amila Senadheera

Reputation: 13265

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>

Upvotes: 3

Related Questions