1

I am making a collapsible section using reactstrap Collapse.

The data received are nested and I am in the need to display the collapse button at second level and the third level will have the data to be displayed.

No problem in this level of display and it already display the data at third level on click of button.

Problem: On click of any button, all the collapsible section gets opened instead of the clicked one.

Requirement: Only the clicked section's token(s) needs to get displayed and on click of the button again it should be collapsed back and it should not have any relation with any other items.

Working Snippet as follows,

const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]

const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
  }
  
  toggle = () =>
  this.setState(s => ({
    open: !s.open
  }));

  render() {
  console.log();
    return <div> 
      {
        data.map((levelOneItem, i) => {
          return(
          <div>
          <div> Order Id:  {levelOneItem.orderId} </div>
          {
            levelOneItem.orderParts.map((levelTwoItem, j) => {
               return(
                  <div>
                  <div> Order Part Id: {levelTwoItem.orderPartsId} </div>
                  <Button onClick={this.toggle}>Display Token</Button>
                  <Collapse isOpen={this.state.open}>
                    {
                       <div>
                        {levelTwoItem.regTokens.map((levelThreeItem, k) => {
                          return(<span> {levelThreeItem} </span>)
                        })
                     }
                       </div>
                    }
                  </Collapse>
                  </div>
               )
            })
          }
          </div>
          )
        })
      }
    </div>;
  }

}


ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>

Kindly help me to achieve the result of toggling only on the selected item for the nested data provided.

aynber
  • 22,380
  • 8
  • 50
  • 63

1 Answers1

0

Add an identifying index as params for your handler function would be fine.

this.state = {
  open: ''
};

toggle = idx => () => {
  this.setState(prevState => ({open: prevState.open === idx ? '' : idx}));
}

<Button onClick={this.toggle(i)}>
<Collapse isOpen={open === i}>

Update

If you want them to be independent, you need an Array to store each status instead.

this.state = {
  open: [0, 2]                                // Initial opened item's index
};

toggle = idx => () => {
  this.setState(prevState => ({
    open: this.state.open.includes(idx)       // check whether been expanded
      ? prevState.open.filter(x => x !== idx) // if yes, remove from list
      : [...prevState.open, idx]}             // if no, add to list
    ))
}

<Button onClick={this.toggle(i)}>
<Collapse isOpen={open.includes(i)}>

const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]

const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      open: [0, 2]  // Initial opened item's index
    };
  }
  
  toggle = idx => () => {
    this.setState(prevState => ({open: this.state.open.includes(idx) ? prevState.open.filter(x => x !== idx) : [...prevState.open, idx]})
  )}
  render() {
  const { open } = this.state;
    return <div> 
      {
        data.map((levelOneItem, i) => {
          return(
          <div>
          <div> Order Id:  {levelOneItem.orderId} </div>
          {
            levelOneItem.orderParts.map((levelTwoItem, j) => {
               return(
                  <div>
                  <div> Order Part Id: {levelTwoItem.orderPartsId} </div>
                  <Button onClick={this.toggle(i)}>Display Token</Button>
                  <Collapse isOpen={open.includes(i)}>
                    {
                       <div>
                        {levelTwoItem.regTokens.map((levelThreeItem, k) => {
                          return(<span> {levelThreeItem} </span>)
                        })
                     }
                       </div>
                    }
                  </Collapse>
                  </div>
               )
            })
          }
          </div>
          )
        })
      }
    </div>;
  }

}


ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>
keikai
  • 14,085
  • 9
  • 49
  • 68
  • Thanks for your answer.. As like I mentioned in question, It should not related with any other item.. Here toggle should happen only on individual item.. Here in this solution, if i open first then click on second, it closes first which is not intended.. –  Mar 30 '20 at 13:24
  • @TestUser I mean to do it, if you need them to be independent, use an Array to save the state, I will update the answer – keikai Mar 30 '20 at 13:25
  • Can you also add the detail of how to make all the collapsible button on initial stage (without clicking when entering into the page) .. I just want to know how it can be handled.. So could you please do it that as well as additional detail.. –  Apr 09 '20 at 00:39
  • Okay I think you have missed index 1 and hence the second is not opened.. But I cannot hard code the index like this say if I have ```n``` items ```levelOneItem.orderParts``` how can we declare like this?? Hope you got my point.. The index cannot be hardcoded and I don't know how many items will be as collapsible so this won't work.. –  Apr 09 '20 at 02:53
  • @TestUser You can choose base on your customized demand, make an Array of index finally would be fine. I just intend to make an explanation for compare so left `[0, 2]` rather than `[0, 1, 2]` for purpose, the final implementation remains uncertain since there is no related code. Maybe you could open a new post about it if you are still facing further questions. Like `How can I acquire the part of data based on the condition of ... and make an index array of it` – keikai Apr 09 '20 at 02:59
  • Here is the new question I have made https://stackoverflow.com/q/61113351/12856847 I am in the need to open all the collapsible items on page load and again on button click can have this same scenario of open/close.. –  Apr 09 '20 at 03:11