skip to Main Content

I have two lists of accordions. I want both accordion sets to be in sync with each other. For example, if I open accordion 1 in the left, I want accordion 1 in the right to be open and vice versa. I am already maintaining the state to keep the accordions open by default Please help me on what I am doing wrong.

This is my code:

    import * as React from 'react';
    import Accordion from '@mui/material/Accordion';
    import AccordionDetails from '@mui/material/AccordionDetails';
    import AccordionSummary from '@mui/material/AccordionSummary';
    import Typography from '@mui/material/Typography';
    import ExpandMoreIcon from '@mui/icons-material/ExpandMore';

    export default function ControlledAccordions() {
      const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});

      const arr = [1, 2, 3];

      React.useEffect(() => {
        setExpanded(
          arr.reduce((a, c) => {
            a[`panel${c}`] = true;
            return a;
          }, {} as Record<string, boolean>)
        );
      }, []);

      const handleChange =
        (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => {
          setExpanded({
            ...expanded,
            [panel]: isExpanded,
          });
        };

      console.log('expanded', expanded);

      return (
        <div
          style={{
            display: 'flex',
            flexDirection: 'row',
          }}
        >
          <div>
            {arr.map((i) => (
              <Accordion
                expanded={expanded[`panel${i}`]}
                onChange={handleChange(`panel${i}`)}
              >
                <AccordionSummary
                  expandIcon={<ExpandMoreIcon />}
                  aria-controls="panel1bh-content"
                  id={`leftpanel${i}bh-header`}
                >
                  <Typography sx={{ width: '33%', flexShrink: 0 }}>
                    General settings
                  </Typography>
                  <Typography sx={{ color: 'text.secondary' }}>
                    I am an accordion
                  </Typography>
                </AccordionSummary>
                <AccordionDetails>
                  <Typography>
                    Nulla facilisi. Phasellus sollicitudin nulla et quam mattis
                    feugiat. Aliquam eget maximus est, id dignissim quam.
                  </Typography>
                </AccordionDetails>
              </Accordion>
            ))}
          </div>
          <div>
            {arr.map((i) => (
              <Accordion
                expanded={expanded[`panel${i}`]}
                onChange={handleChange(`panel${i}`)}
              >
                <AccordionSummary
                  expandIcon={<ExpandMoreIcon />}
                  aria-controls="panel1bh-content"
                  id={`rightpanel${i}bh-header`}
                >
                  <Typography sx={{ width: '33%', flexShrink: 0 }}>
                    General settings
                  </Typography>
                  <Typography sx={{ color: 'text.secondary' }}>
                    I am an accordion
                  </Typography>
                </AccordionSummary>
                <AccordionDetails>
                  <Typography>
                    Nulla facilisi. Phasellus sollicitudin nulla et quam mattis
                    feugiat. Aliquam eget maximus est, id dignissim quam.
                  </Typography>
                </AccordionDetails>
              </Accordion>
            ))}
          </div>
        </div>
      );
    }

My Stackblitz URL: https://stackblitz.com/edit/react-7qktzd?file=Demo.tsx

Please advice.

2

Answers


  1. Chosen as BEST ANSWER

    I was able to fix it by checking if its empty and render the accordion only if the state value is not empty.

    https://stackblitz.com/edit/react-7qktzd-imzy6p?file=Demo.tsx


  2. I think this can be fixed if you remove the first useEffect since you are just trying to set the initial state of the accordions as opened? And arr doesn’t change after that anyways.

    Here’s how I would go about it:

    1. remove the first useEffect and use arr to set the initial state of expanded:
    // don't need this
    // React.useEffect(() => {
    //        setExpanded(
    //          arr.reduce((a, c) => {
    //            a[`panel${c}`] = true;
    //            return a;
    //          }, {} as Record<string, boolean>)
    //        );
    //      }, []);
    
    
    const [expanded, setExpanded] = React.useState<Record<string, boolean>>(
       // same reduce function from useEffect above
        arr.reduce((a, c) => {
          a[`panel${c}`] = true;
          return a;
        }, {} as Record<string, boolean>)
      );
    
    1. update Accordion expanded attribute to whatever is in expanded state, which will be guaranteed to have a value by the time it gets here:
    <Accordion
                key={i}
                expanded={expanded[`panel${i}`]}
                onChange={handleChange(`panel${i}`)}
              >
    

    And all together:

    import * as React from 'react';
    import Accordion from '@mui/material/Accordion';
    import AccordionDetails from '@mui/material/AccordionDetails';
    import AccordionSummary from '@mui/material/AccordionSummary';
    import Typography from '@mui/material/Typography';
    import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
    
    export default function ControlledAccordions() {
      const arr = [1, 2, 3];
    
      const [expanded, setExpanded] = React.useState<Record<string, boolean>>(
        arr.reduce((a, c) => {
          a[`panel${c}`] = true;
          return a;
        }, {} as Record<string, boolean>)
      );
      const handleChange =
        (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => {
          setExpanded({
            ...expanded,
            [panel]: isExpanded,
          });
        };
    
      console.log('expanded', expanded);
    
      return (
        <div
          style={{
            display: 'flex',
            flexDirection: 'row',
          }}
        >
          <div>
            {arr.map((i) => (
              <Accordion
                key={i} 
                expanded={expanded[`panel${i}`]} // removed || true here
                onChange={handleChange(`panel${i}`)}
              >
                <AccordionSummary
                  expandIcon={<ExpandMoreIcon />}
                  aria-controls="panel1bh-content"
                  id={`panel${i}bh-header`}
                >
                  <Typography sx={{ width: '33%', flexShrink: 0 }}>
                    General settings
                  </Typography>
                  <Typography sx={{ color: 'text.secondary' }}>
                    I am an accordion
                  </Typography>
                </AccordionSummary>
                <AccordionDetails>
                  <Typography>
                    Nulla facilisi. Phasellus sollicitudin nulla et quam mattis
                    feugiat. Aliquam eget maximus est, id dignissim quam.
                  </Typography>
                </AccordionDetails>
              </Accordion>
            ))}
          </div>
          <div>
            {arr.map((i) => (
              <Accordion
                key={i}
                expanded={expanded[`panel${i}`]}
                onChange={handleChange(`panel${i}`)}
              >
                <AccordionSummary
                  expandIcon={<ExpandMoreIcon />}
                  aria-controls="panel1bh-content"
                  id={`panel${i}bh-header`}
                >
                  <Typography sx={{ width: '33%', flexShrink: 0 }}>
                    General settings
                  </Typography>
                  <Typography sx={{ color: 'text.secondary' }}>
                    I am an accordion
                  </Typography>
                </AccordionSummary>
                <AccordionDetails>
                  <Typography>
                    Nulla facilisi. Phasellus sollicitudin nulla et quam mattis
                    feugiat. Aliquam eget maximus est, id dignissim quam.
                  </Typography>
                </AccordionDetails>
              </Accordion>
            ))}
          </div>
        </div>
      );
    }
    
    

    But you should still consider moving things to a parent component like the react docs says here: https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search