Search code examples
reactjsmaterial-uiexpand

React JS - Material UI ListItem (with Collapse API) onClick expands/collapse all sub list items instead of the selected one


I'm implementing Expand/Collapse feature of List using React JS - Material UI ListItem (with Collapse API)

When I click on the ListItem it expands/collapse all sub list items instead of the selected one.

Here is the sample code. Looks like I'm improperly setting the key value in one of the element, but couldn't figure it out. Can someone help me? Please let me know if the information is inadequate

Jaffar

class CategoriesResults extends Component {
  constructor(props) {
    super(props);
    this.state = {
      open: false
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log("Handle Clicked....");
     this.setState(prevState => ({
       open: !prevState.open
     }));
  }

  render() {
    const docs = data.documents;  //this coming from a json file, please see below for the sample json
     return (
      <div>
        <List component='nav' aria-labelledby='nested-list-subheader'>
          {docs.map(doc => {
            return (
              <div key={doc.Id}>
                <ListItem button key={doc.Id} onClick={this.handleClick}>
                  <ListItemText primary={doc.Name} />
                  {this.state.open ? <ExpandLess /> : <ExpandMore />}
                </ListItem>
                <Collapse
                  key={doc.Sheets.Id}
                  in={this.state.open}
                  timeout='auto'
                  unmountOnExit
                >
                  <List component='li' disablePadding key={doc.Id}>
                    {doc.Sheets.map(sheet => {
                      return (
                        <ListItem button key={sheet.Id}>
                          <ListItemIcon>
                            <InsertDriveFileTwoToneIcon />
                          </ListItemIcon>
                          <ListItemText key={sheet.Id} primary={sheet.Title} />
                        </ListItem>
                      );
                    })}
                  </List>
                </Collapse>
                <Divider />
              </div>
            );
          })}
        </List>     
      </div>
    );
  }
}


**Sample JSON**

{
  "documents": [
    {
      "Id": 1,
      "Name": "Category 1",
      "Sheets": [
        {
          "Id": 1,
          "Title": "Title1 "
        },
        {
          "Id": 2,
          "Title": "Title 2"
        },
        {
          "Id": 3,
          "Title": "Title 3"
        }
      ]
    }
}


Solution

  • All of your ListItems are expanded based on the same state.
    If open is true, all of them get in=true in their Collapse component, so all of them are expanded.
    To fix that, you should extract your expandable ListItem to a seperate component, which will manage it's own state:

     class CustomizedListItem extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
            open: false
          };
          this.handleClick = this.handleClick.bind(this);
        }
    
        handleClick() {
          console.log("Handle Clicked....");
           this.setState(prevState => ({
             open: !prevState.open
           }));
        }
    
      render(){
      const { doc } = this.props;
      return (
        <div>
          <ListItem button key={doc.Id} onClick={this.handleClick}>
            <ListItemText primary={doc.Name} />
            {this.state.open ? <ExpandLess /> : <ExpandMore />}
          </ListItem>
          <Collapse
            key={doc.Sheets.Id}
            in={this.state.open}
            timeout='auto'
            unmountOnExit
          >
          <List component='li' disablePadding key={doc.Id}>
            {doc.Sheets.map(sheet => {
              return (
                <ListItem button key={sheet.Id}>
                  <ListItemIcon>
                    {/* <InsertDriveFileTwoToneIcon /> */}
                  </ListItemIcon>
                  <ListItemText key={sheet.Id} primary={sheet.Title} />
                </ListItem>
              );
            })}
          </List>
        </Collapse>
        <Divider />
        </div>
        )
      }
    }
    
    export default class CategoriesResults extends React.Component {
      render() {
        const docs = data.documents;  //this coming from a json file, please see below for the sample json
         return (
          <div>
            <List component='nav' aria-labelledby='nested-list-subheader'>
              {docs.map(doc => {
                return (
                  <CustomizedListItem key={doc.id} doc={doc} />
                );
              })}
            </List>     
          </div>
        );
      }
    }
    

    I used class component because you used it in your question. If you want to see the same solution with react-hooks, let me know :)

    Edit Invisible Backdrop