user8791790
user8791790

Reputation:

React accordion with expand all and collapse all

I’m new to react and this original code by Elaine at https://codepen.io/elainehuang/pen/XVNXrJ has helped me create an accordion with expand and collapse all button.

I’d like to modify this solution so that I can fetch not only the title but also the accordion content using a map function. Any help in this regard would be appreciated.

class Accordion extends React.Component {
  render() {
    const { title, expand, onClick } = this.props;
    
    return (
      <div>
        <dt className={expand ? 'title is-expanded' : 'title'} onClick={onClick}>
          {title}
        </dt>
        <dd className={expand ? 'content is-expanded' : 'content'} onClick={onClick}>
          <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eu interdum diam. Donec interdum porttitor risus non bibendum. Maecenas sollicitudin eros in quam imperdiet placerat. Cras justo purus, rhoncus nec lobortis ut, iaculis vel ipsum. Donec dignissim arcu nec elit faucibus condimentum. Donec facilisis consectetur enim sit amet varius. Pellentesque justo dui, sodales quis luctus a, iaculis eget mauris.
          </p>
        </dd>
      </div>
    );
    
  }
}
class Application extends React.Component {  
  constructor (props) {
    super(props);
    this.state = {
      block1: true,
      block2: false,
      block3: false,
    };
  }
  
  toggle = (index) => () => {
    this.setState({ [`block${index}`]: !this.state[`block${index}`] });
  }
  
  toggleExpand = (expand = false) => () => {
    this.setState({
      block1: expand,
      block2: expand,
      block3: expand,
    });
  }
  
  render() {
    const accordionList = [{ title: 'First Accordion' }, { title: 'Second Accordion' }, { title: 'Third Accordion' }];
    
    return <div className="container">
      <h1>React Accordion</h1>
      <button type="button" className="btn" onClick={this.toggleExpand(true)}>Expand All</button>
      <button type="button" className="btn" onClick={this.toggleExpand()}>Collapse All</button>
      <dl className="accordion">
        {
          accordionList.map((item, index) => (
            <Accordion title={item.title} onClick={this.toggle(index + 1)} expand={this.state[`block${index+1}`]} />
          ))
        }
      </dl>
    </div>;
  }
}

React.render(<Application />, document.getElementById('app'));

Upvotes: 3

Views: 13409

Answers (1)

macborowy
macborowy

Reputation: 1534

Change Accordion to:

class Accordion extends React.Component {
  render() {
    const { title, content, expand, onClick } = this.props;
    
    return (
      <div>
        <dt className={expand ? 'title is-expanded' : 'title'} onClick={onClick}>
          {title}
        </dt>
        <dd className={expand ? 'content is-expanded' : 'content'} onClick={onClick}>
          <p>{content}</p>
        </dd>
      </div>
    ); 
  }
}

and Application to:

class Application extends React.Component {  
  // ...
  
  render() {
      const accordionList = [{ title: 'First Accordion', content: 'Content 1' }, { title: 'Second Accordion', content: 'Content 2' }, { title: 'Third Accordion', content: 'Content 3' }];

      // ...

      <dl className="accordion">
        {
          accordionList.map((item, index) => (
            <Accordion title={item.title} content={item.content} onClick={this.toggle(index + 1)} expand={this.state[`block${index+1}`]} />
          ))
        }
      </dl>

      // ...
}

Update 1

I had another doubt. The number of blocks might not always be 3 then how can I dynamically set the block values (block1, block2 etc) to false in this.state and to expand in this.setState?

All of the changes are required in Application component. To make it work you need:

  • Move accordionList from render() to the state. I add id property to each element (and then use it inside map()). It makes sense to keep accordionList in the state instead of render() function.
  • Tweak toggle(id) and toggleExpand(expand) to use block id instead of array index.

class Application extends React.Component {  
  constructor (props) {
    super(props);
    this.state = {
      blocks: [
        { id: 1, title: 'First Accordion', content: 'Content 1', expanded: true },
        { id: 2, title: 'Second Accordion', content: 'Content 2', expanded: false },
        { id: 3, title: 'Third Accordion', content: 'Content 3', expanded: false },
        { id: 4, title: 'Fourth Accordion', content: 'Content 4', expanded: false }
      ]
    };
  }
    
  toggle(id) {
    this.setState((prevState) => {
      const index = prevState.blocks.findIndex(item => item.id == id);
      prevState.blocks[index].expanded = !prevState.blocks[index].expanded;
      
      return { blocks: prevState.blocks };
    });
  }
  
  toggleExpand(expand) {
    this.setState((prevState) => {
      const blocks = prevState.blocks.map(item => {
        item.expanded = expand;
        return item;
      });
      
      return { blocks };
    });
  }
  
  render() {   
    return (
      <div className="container">
        <h1>React Accordion</h1>
        <button type="button" className="btn" onClick={() => this.toggleExpand(true)}>Expand All</button>
        <button type="button" className="btn" onClick={() => this.toggleExpand(false)}>Collapse All</button>
        <dl className="accordion">
          {
            this.state.blocks.map(item => (
              <Accordion key={item.id} title={item.title} content={item.content} expand={item.expanded} onClick={() => this.toggle(item.id)} />
            ))
          }
        </dl>
      </div>
    );
  }
}

class Accordion extends React.Component {
  render() {
    const { title, content, expand, onClick } = this.props;
    
    return (
      <div>
        <dt className={expand ? 'title is-expanded' : 'title'} onClick={onClick}>
          {title}
        </dt>
        <dd className={expand ? 'content is-expanded' : 'content'} onClick={onClick}>
          <p>{content}</p>
        </dd>
      </div>
    ); 
  }
}

ReactDOM.render(<Application />, document.getElementById('app'));
@import url("https://fonts.googleapis.com/css?family=PT+Sans");
html, body, #app {
  height: 100%;
}

.container {
  min-height: 100%;
  padding: 30px;
  font-family: 'PT Sans';
  text-align: center;
}
.container h1 {
  text-align: center;
  color: #1569a8;
}
.container .btn {
  display: inline-block;
  margin-bottom: 20px;
  border: 1px solid #1569a8;
  background: white;
  color: #1569a8;
  padding: 5px 10px;
  border-radius: 5px;
  margin-right: 5px;
  font-size: 15px;
  cursor: pointer;
  outline: none;
}
.container .btn:hover {
  background: #1569a8;
  color: white;
  -webkit-transition: .5s;
  transition: .5s;
}

.accordion {
  margin: 0 auto;
  width: 80%;
}
.accordion .title {
  padding: 30px 30px;
  cursor: pointer;
  -webkit-transform: translate3d(0, 0, 0);
          transform: translate3d(0, 0, 0);
  color: white;
  position: relative;
  font-size: 20px;
  background: #1569a8;
  margin-bottom: -1px;
  border-bottom: 1px solid #0e4671;
  text-align: left;
}
.accordion .title::after {
  content: "+";
  font-size: 18px;
  color: white;
  -webkit-transition: -webkit-transform .5s ease-in-out;
  transition: -webkit-transform .5s ease-in-out;
  transition: transform .5s ease-in-out;
  transition: transform .5s ease-in-out, -webkit-transform .5s ease-in-out;
  position: absolute;
  right: 30px;
  font-family: monospace;
}
.accordion .title.is-expanded {
  -webkit-transition: background .5s;
  transition: background .5s;
  background: #0e4671;
}
.accordion .title.is-expanded::after {
  content: "-";
  -webkit-transform: rotate(-360deg);
          transform: rotate(-360deg);
}
.accordion .content {
  overflow: hidden;
  max-height: 0;
  -webkit-transition: max-height .5s;
  transition: max-height .5s;
  margin: 0;
  padding: 0 30px;
  border: solid 1px #eeeeee;
  border-top: 0;
  background: #e8f4fc;
}
.accordion .content p {
  padding: 30px 0;
  margin: 0;
  opacity: 0;
  -webkit-transition: .5s;
  transition: .5s;
}
.accordion .content.is-expanded {
  max-height: 500px;
  overflow: hidden;
}
.accordion .content.is-expanded p {
  opacity: 1;
}
.accordion:after {
  width: 100%;
  height: 10px;
  display: block;
  background: #0e4671;
  content: '';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Upvotes: 2

Related Questions