How to add dynamic content to React Material UI expansion panels whilst keeping only one active tab at at time functionality


I have WebGL/React project that generates a list of users from a mock data on click.

I want this content to appear in an accordion and, as I've had good experience with material ui before I thought I'd use their expansion panel.

It works well straight from the demo page, however if I wanted to map over my user data base and populate the expansion panels with this instead, it appears to get rid of the handy functionality.

I would like the first expansion panel open by default and then as you click on one panel, any other panels close, which is the default behaviour from the example.

Material ui example

When I pass in props

      <ExpansionPanel
        square
        expanded={expanded === "panel1"}
        onChange={handleChange("panel1")}
      >
        <ExpansionPanelSummary
          aria-controls="panel1d-content"
          id="panel1d-header"
        >
          {props.country}
        </ExpansionPanelSummary>
        <ExpansionPanelDetails>{props.children}</ExpansionPanelDetails>
      </ExpansionPanel>
    </div>

then use it here....

              {users &&
                users.map(user => (
                  <Accordion title={user.name} key={user.name}>
                    <div className="overlay-container">
                      <div className="overlay overlay-anim">
                        <div className="overlay-content-container">
                          <div className="name-container">
                            <h1 key={user.id} className="user_name">
                              {user.name}
                            </h1>
                          </div>
                        </div>
                      </div>
                    </div>
                  </Accordion>
                ))}

It opens all the panels by default and doesn't close the others when one is active. (again not real data and the gif makes it a little buggy but I can't generate an example as the code base is too huge).

enter image description here

Would anyone have some ideas or examples of how to achieve this?

EDIT

as per below suggestion have added added an id into the mapping function and adapted the expansion component, unfortunately getting the same effect/issues

  const [expanded, setExpanded] = React.useState("panel" + props.id);

  const handleChange = panel => (event, newExpanded) => {
    setExpanded(newExpanded ? panel : false);
  };

  return (
    <div>
      <ExpansionPanel
        expanded={expanded === "panel" + props.i}
        onChange={handleChange("panel" + props.i)}
      >
        <ExpansionPanelSummary
          aria-controls={"panel" + props.id + "d" + "-content"}
          id={"panel" + props.id + "d" + "-header"}
        >
          {props.country}
        </ExpansionPanelSummary>
        <ExpansionPanelDetails>{props.children}</ExpansionPanelDetails>
      </ExpansionPanel>
    </div>
  );
}```

Hard to say what you are doing with the ExpansionPanel, but it would appear you have same

<ExpansionPanel expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>

for everyone of them, you need to have unique names (panel1, panel2, panel3) for them.

EDIT:

You could add iterator into the map function:

users.map((user, i) => (

and have i passed to the ExpansionPanel as prop for

<ExpansionPanel expanded={expanded === 'panel' + props.i} onChange={handleChange('panel' + props.i)}>

EDIT #2: updated answer with working code and reason why yours didn't work.

Main function, note that u should add the useState here and give them to the CustomizedExpansionPanels child.

example.jsx

import React, { useState } from 'react'
import { withStyles } from '@material-ui/core/styles'
import CustomizedExpansionPanels from './TestTab.jsx'

const styles = (theme) => ({
/* ... your styles... */
})

const users = [
    { name: '5001', color: 'green', type: 'None' },
    { name: '5002', color: 'blue', type: 'Glazed' },
    { name: '5003', color: 'red', type: 'Chocolate' },
    { name: '5004', color: 'orange', type: 'Maple' }
]

function Example(props) {
    const [expanded, setExpanded] = useState('panel_0') // change 0 to the number u want to be open by default
    return (
            <div>
                {users.map((user, i) => CustomizedExpansionPanels(user, i, expanded, setExpanded))}
            <div/>
    )

export default withStyles(styles, { withTheme: true })(Example)

TestTab.jsx

import React from 'react'
import { withStyles } from '@material-ui/core/styles'
import MuiExpansionPanel from '@material-ui/core/ExpansionPanel'
import MuiExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'
import MuiExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
import Typography from '@material-ui/core/Typography'

const ExpansionPanel = withStyles({
    root: {
        border: '1px solid rgba(0, 0, 0, .125)',
        boxShadow: 'none',
        '&:not(:last-child)': {
            borderBottom: 0,
        },
        '&:before': {
            display: 'none',
        },
        '&$expanded': {
            margin: 'auto',
        },
    },
    expanded: {},
})(MuiExpansionPanel)

const ExpansionPanelDetails = withStyles((theme) => ({
    root: {
        padding: theme.spacing(2),
    },
}))(MuiExpansionPanelDetails)

const ExpansionPanelSummary = withStyles({
    root: {
        backgroundColor: 'rgba(0, 0, 0, .03)',
        borderBottom: '1px solid rgba(0, 0, 0, .125)',
        marginBottom: -1,
        minHeight: 56,
        '&$expanded': {
            minHeight: 56,
        },
    },
    content: {
        '&$expanded': {
            margin: '12px 0',
        },
    },
    expanded: {},
})(MuiExpansionPanelSummary)

export default function CustomizedExpansionPanels(user, id, expanded, setExpanded) {
    const handleChange = (panel) => (event, newExpanded) => {
        setExpanded(newExpanded ? panel : false)
    }
    const { name, color, type } = user

    return (
        <div>
            <ExpansionPanel square expanded={expanded === `panel_${id}`} onChange={handleChange(`panel_${id}`)}>
                <ExpansionPanelSummary aria-controls={`panel_${id}d-content`} id={`panel_${id}d-header`}>
                    <Typography style={{ color }}>{`Collapsible Group Item #${id}`}</Typography>
                </ExpansionPanelSummary>
                <ExpansionPanelDetails>
                    <Typography>
                        {`name: ${name} type: ${type}`}
                    </Typography>
                </ExpansionPanelDetails>
            </ExpansionPanel>
        </div>
    )
}

Hard to say for sure, but it looks like you are looping the creation of one and same panel where the extended hook exists in each one of them and has the value id of it's own panel => not related to the other panels opening and closing. You need to create each ExpansionPanel with it's own variables and have 1 hook to control them all.