de rigueur
de rigueur

Reputation: 61

react-bootstrap Accordion not collapsing with map

I have an array of items I would like to create Panels out of which would eventually be inserted into the Accordion.

In one file, I have:

var items = this.state.contents.map(function(content, index) {
  return <Content {...content}, key={index}/>
};

return (
  <Accordion>
    {items}
  </Accordion>
);

In another file called Content I have:

return(
  <Panel header={this.props.header} eventKey={this.props.key}>
    {this.props.body}
  </Panel>
);

When I have the Accordion and Panel in the same file, they work. But when I generate the Panel using map after splitting them into two files, it doesn't seem to collapse.

Upvotes: 3

Views: 5435

Answers (5)

TonyLoops
TonyLoops

Reputation: 1

The easiest way to do it is making a condition inside the eventKey parameter.

 <Card key={item.id}> 
    <Card.Header>
      <Accordion.Toggle as={Button} variant="link" eventKey={index === 0 ? '0' : index}>
        {item.title} 
      </Accordion.Toggle>
    </Card.Header>
    <Accordion.Collapse eventKey={index === 0 ? '0' : index}>
      <Card.Body>
             CARD BODY
      </Card.Body>
    </Accordion.Collapse>
  </Card>

Upvotes: 0

Ethan
Ethan

Reputation: 21

For anyone using Accordion.Toggle, this is how I got the accordion to collapse on map.

In the parent component of the Accordion, add an activeKey value to the state. Create a function to handle the active key change that will be passed down during your map to each child component of the accordion. Be sure to bind that function to the parent component.

this.state = {
  items = []
  activeKey: ''
}

handleActiveKeyChange = activeKey => {
  if (this.state.activeKey === activeKey) {
    //allows us to close expanded item by clicking its toggle while open
    activeKey = -1
  }
  this.setState({ activeKey })
}

Pass the active key state and function accordingly in your render method:

<Accordion activeKey={this.state.activeKey}>
  {this.state.items.map((item, index) => (
    <Item eventKey={index} handleActiveKeyChange={this.handleActiveKeyChange} />
  ))}
</Accordion

In your child (item) element's Accordion toggle, add the passed function and props:

<Card>
  <Card.Header>
    <Accordion.Toggle
      eventKey={this.props.eventKey}
      onClick={() => this.props.handleActiveKeyChange(this.props.eventKey)}
    >
      //+ icon here
    </Accordion.Toggle>
  </Card.Header>
  //card content here 
</Card

Upvotes: 2

Lalit Joshi
Lalit Joshi

Reputation: 66

Here is the solution

<Accordion>
  {this.item.map(item =>
     <AcPanel key={item.id} item={item} eventKey={item.id} />
  )}
</Accordion>

In AcPanel Class use {...this.props} for fetch all properties and assign values on your parent class like i used "eventKey={item.id}".

<Panel header={`Collapsible Group Item`} bsClass='class-name' {...this.props}>
content here
</Panel>

Upvotes: 2

David Mason
David Mason

Reputation: 2957

I found a solution that lets you use a component in a separate file.

I figured the problem must be to do with the Accordion not being able to pass some props down to the Panel because they go to the wrapping component instead, so I tried collecting all the props that weren't mine and passing them to the panel:

// make sure you have all your own props specified before ...props
// so that they can't mess up the panel.
const { body, ...props } = this.props

return(
  <Panel {...props}>
    {body}
  </Panel>
);

If you want to specify any props on the Panel, make sure they go after the collected props so that they take precedence over any defaults:

const { body, ...props } = this.props

return(
  <Panel {...props}
         bsStyle="info">
    {body}
  </Panel>
);

If you choose any props names the same as whatever Accordion is passing to Panel, you could clobber something and break it - if you're concerned, it should be straightforward to follow the code and see what is being passed on.

Upvotes: 1

de rigueur
de rigueur

Reputation: 61

react-bootstrap seems to complain if the children of an Accordion are not Panels, and by wrapping Panel inside of my Content class I was doing just that.

To solve this problem I had to step away from React's convention. Rather than creating a class Content, I created another js file rather than a react.js file. By treating this file like a normal js file, I was able to call on a function inside of this file which maps over each 'this.state.contents' and return a Panel object.

This whole file will return an array of Panel class.

Upvotes: 0

Related Questions