Home > Mobile >  Accordion and targeting state in react component
Accordion and targeting state in react component

Time:09-08

I am building this simple accordion and trying to grab the state (isActive) from the App component, the problem here is all contents open and close together when I try to open each one separately. these are two different components

Although the problem can be fixed if I create the state in the Accordion component, but I need to find a way to fix the problem when the state is passed from App component because I will need to use that state in App component for other things

can anyone help with example please

thanks

import React, { useState } from "react";
import Accordion from "./Accordion";

const App = () => {

  const [isActive, setIsActive] = useState(false)

  const accordionData = [
    {
      title: "Section 1",
      content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
       
    
    },
    {
      title: "Section 2",
      content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
   
   
    },
    {
      title: "Section 3",
      content: `Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quis`
     
     
    }
  ];

  return (
    <div>
      <h1>React Accordion Demo</h1>
      <div className="accordion">
        {accordionData.map(({ title, content }) => (
          <Accordion
            setIsActive={setIsActive}
            isActive={isActive}
            title={title}
            content={content}
          />
        ))}
      </div>
    </div>
  );
};

export default App;

This is the Accordion component

import React from "react";

const Accordion = ({ title, content, isActive, setIsActive }) => {
  return (
    <div className="accordion-item">
      <div className="accordion-title" onClick={() => setIsActive(!isActive)}>
        <div>{title}</div>
        <div>{isActive ? "-" : " "}</div>
      </div>
      {isActive && <div className="accordion-content">{content}</div>}
    </div>
  );
};

export default Accordion;

This is what happens when I click on one of the buttons it opens all contents

click on image

screenshot of the UI

CodePudding user response:

You need to pass a function that calls setIsActive as a props, try that in App:

const App = () => {

  const [isActive, setIsActive] = useState(false)

  ...
  
  const setIsItemActive = (isItemActive) => {
    setIsActive(isItemActive)
  }

  return (
    <div>
      <h1>React Accordion Demo</h1>
      <div className="accordion">
        {accordionData.map(({ title, content }) => (
          <Accordion
            setIsActive={setIsItemActive}
            isActive={isActive}
            title={title}
            content={content}
          />
        ))}
      </div>
    </div>
  );
};

export default App;

CodePudding user response:

I've introduced id and active classes to the dataset (you can do this "in-component" if you prefer). When a panel heading is clicked get the panel id, and then map over the panel state returning a new array of updated objects - if the panel id matches the id from the clicked heading set active to true, otherwise false.

Update the new state to cause a new render. When you map over the panel state to produce the JSX you can check the active prop of each panel object and set the text to show/hide.

const { useState } = React;

function Example({ data }) {

  // Use the state to hold the panel data, introducing
  // an active prop
  const [ panels, setPanels ] = useState(data);

  function handleClick(e) {
    
    // When the accordion is clicked check that
    // the clicked element was the heading...
    if (e.target.matches('h4')) {
      
      // Get the id from the parent element (the panel)
      const panel = e.target.closest('.panel');
      const { id } = panel.dataset;
      
      // `map` over the panel data and, if the id matches
      // the current panel id return an object with the
      // active prop set to true, otherwise return an object
      // with the prop set to false
      const updated = panels.map(panel => {
        if (panel.id ===  id) {
          return { ...panel, active: true };
        }
        return { ...panel, active: false };
      });
      
      // Update the panel state with the new array
      setPanels(updated);
    }
  }

  return (
    <section  onClick={handleClick} >
      
      {panels.map(panel => {
        
        const { id, title, content, active } = panel;
        
        // Determine whether the `p` element should be
        // shown based on the panel's active prop
        return (
          <section
            key={id}
            data-id={id}
            className="panel"
          >
            <h4>{title}</h4>
            <p className={active && 'active'}>{content}</p>
          </section>
        );
      
      })}
    
    </section>
  );

}

const data = [
  { id: 1, title: 'Heading 1', content: 'Text 1', active: false },
  { id: 2, title: 'Heading 2', content: 'Text 2', active: false },
  { id: 3, title: 'Heading 3', content: 'Text 3', active: false }
];

ReactDOM.render(
  <Example data={data} />,
  document.getElementById('react')
);
.panel p { display: none; }
.panel h4, .panel p { padding: 0.25em; }
.panel h4:hover { background-color: lightgreen; cursor: pointer; }
.active { display: block !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

  • Related