Source:  Twitter logo

Currently my list all have collapses but they are linked to one state for "open" so if I open one list, all the other lists open. What is the best way to keep the collapses separate from each other without having a lot of states for each list.

EDIT: The app is going through an infinite loop

App.tsx

interface IState {
error: any,
intro: any,
threads: any[],
title: any,
}

export default class App extends React.Component<{}, IState> {
    constructor (props : any) {
        super (props);

        this.state = {
            error: "",
            intro: "Welcome to RedQuick",
            threads: [],
            title: ""
        };

        this.getRedditPost = this.getRedditPost.bind(this)
        this.handleClick = this.handleClick.bind(this)
    }

    public getRedditPost = async (e : any) => {
        e.preventDefault();

        const subreddit = e.target.elements.subreddit.value;
        const redditAPI = await fetch('https://www.reddit.com/r/'+ subreddit +'.json');
        const data = await redditAPI.json();

        console.log(data);

        if (data.kind) {
            this.setState({
                error: undefined,
                intro: undefined,
                threads: data.data.children,
                title: data.data.children[0].data.subreddit.toUpperCase()
            });
        } else {
            this.setState({
                error: "Please enter a valid subreddit name",
                intro: undefined,
                threads: [],
                title: undefined
            });
        }
    }

    public handleClick = (index : any)  => {
        this.setState({ [index]: true });
    }

    public render() {
        return (
            <div>
                <Header 
                    getRedditPost={this.getRedditPost}
                />
                <p className="app__intro">{this.state.intro}</p>
                {
                    this.state.error === "" && this.state.title.length > 0 ?
                    <LinearProgress />:
                    <ThreadList 
                        error={this.state.error}
                        handleClick={this.handleClick}
                        threads={this.state.threads}
                        title={this.state.title}
                    />
                }   
            </div>
        );
    }
}

Threadlist.tsx

<div className="threadlist__subreddit_threadlist">
    <List>
        { props.threads.map((thread : any, index : any) => 
            <div key={index} className="threadlist__subreddit_thread">
                <Divider />
                <ListItem button={true} onClick={props.handleClick(index)}/* component="a" href={thread.data.url}*/ >
                    <ListItemText primary={thread.data.title} secondary={<p><b>Author: </b>{thread.data.author}</p>} />
                    {props[index] ? <ExpandLess /> : <ExpandMore />}
                </ListItem>
                <Collapse in={props[index]} timeout="auto" unmountOnExit={true}>
                    <p>POOP</p>
                </Collapse>
                <Divider />
            </div>
        ) }
    </List> 
</div>

You have to create a different state for every collapse, i suggest using setState dynamically with the index you got from the map function, you probably have to pass the index param to the handleClick function and change the state based on that

<div className="threadlist__subreddit_threadlist">
    <List>
        { props.threads.map((thread : any, index : any) => 
            <div key={index} className="threadlist__subreddit_thread">
                <Divider />
                <ListItem button={true} onClick={props.handleClick(index)}/* component="a" href={thread.data.url}*/ >
                    <ListItemText primary={thread.data.title} secondary={<p><b>Author: </b>{thread.data.author}</p>} />
                    {props[index] ? <ExpandLess /> : <ExpandMore />}
                </ListItem>
                <Collapse in={props[index]} timeout="auto" unmountOnExit={true}>
                    <p>POOP</p>
                </Collapse>
                <Divider />
            </div>
        ) }
    </List> 
</div>

Your handleClick should look something like this:

public handleClick = (index : any)  => {
        this.setState({ [index]: true });
    }
9 users liked answer #0dislike answer #09
Ricardo Costa profile pic
Ricardo Costa

I've done this in this way :

function DrawerMenuItems() {
  const [selectedIndex, setSelectedIndex] = React.useState("")

  const handleClick = index => {
    if (selectedIndex === index) {
      setSelectedIndex("")
    } else {
      setSelectedIndex(index)
    }
  }
  return (
    <List
      component="nav"
      aria-labelledby="nested-list-subheader"
      subheader={
        <ListSubheader component="div" id="nested-list-subheader">
          Nested List Items
        </ListSubheader>
      }
    >
      {drawerMenuItemData.map((item, index) => {
        return (
          <List>
            <ListItem
              key={index}
              button
              onClick={() => {
                handleClick(index)
              }}
            >
              <ListItemIcon>
                <item.icon />
              </ListItemIcon>
              <ListItemText primary={item.title} />
              {index === selectedIndex ? <ExpandLess /> : <ExpandMore />}
            </ListItem>
            <Collapse in={index === selectedIndex} timeout="auto" unmountOnExit>
              <List component="div" disablePadding>
                {item.submenu.map((sub, index) => {
                  return (
                    <ListItem button >
                      <ListItemText primary={sub.name} />
                    </ListItem>
                  )
                })}
              </List>
            </Collapse>
          </List>
        )
      })}
    </List>
  )
}
7 users liked answer #1dislike answer #17
Md Abdul Halim Rafi profile pic
Md Abdul Halim Rafi

you can use ant design collapse component also. I have made a nested collapse component with it . basically, which library use is not matters. What matters is how are you passing the data & how are you controlling the active key. Here is an example of mine...

const ProductCatagoryHierarchy = () => {

 const [tree, setTree] = useState([])

 useEffect( async () => {

    const arr2 = [
            {id: 1, name: 'gender', parent: null, parent_id: null },
            {id: 2, name: 'material', parent: null, parent_id: null},
            { id: 3, name: 'male', parent: 1, parent_name: "gender" },
            { id: 5, name: 'female', parent: 1, parent_name: "gender" },
            { id: 4, name: 'shoe', parent: 3, parent_id: "male" },
        ]

        let newarr=[];
        for(let i=0 ; i< arr2.length; i++ ){

            if(arr2[i].id){
                if(newarr[i] != {} ){
                    newarr[i] = {}
                }
                newarr[i].id = arr2[i].id 
            }
            if( arr2[i].name ){
                newarr[i].name = arr2[i].name 
            }
            if( arr2[i].parent ){
                newarr[i].parent = arr2[i].parent 
            }
            if( arr2[i].parent_id ){
                newarr[i].parent_id = arr2[i].parent_id 
            }

            newarr[i].products = arr2[i].products;
        }

        console.log('newarr', newarr );

        let tree = function (data, root) {
            var t = {};
            data.forEach(o => {
                Object.assign(t[o.id] = t[o.id] || {}, o);
                t[o.parent] = t[o.parent] || {};
                t[o.parent].children = t[o.parent].children || [];
                t[o.parent].children.push(t[o.id]);
            });
            return t[root].children;
        }(newarr, undefined);
        
        console.log('tree ', tree);
        setTree(tree)
 }, [])

  const childFunc = (children) => {
        // console.log('children', children );
        let childPanel = children.map((child, i) => {
            return(
                <Collapse /* defaultActiveKey={child.id} */  
                style={{ margin: '10px 0' }}  key={i} 
                expandIcon={({ isActive }) => 
               <CaretRightOutlined rotate={isActive ? 90 : 0} />}
                >
                    <Panel header={child.name} key={child.id}   >
                    <p>  {child?.products?.length > 0 ? 
                    <p> Products: <strong> 
                  { child.products.map(x =>  x.name  ).join(', ') } 
                  </strong> </p> : 'No Product Available'} </p> 

                    {child?.children?.length > 0 ? 
                    childFunc(child.children) : null }
                    </Panel>
                </Collapse>
            )
        })

        return childPanel; 
    }

    return(
        <div>

        <h1> Category Hierarchy </h1>

        {/* <Button onClick={() => onclick()} > test </Button> */}


        <Collapse  >
            {
                tree.map((x,i,l) => {
                console.log('x,i,l', x, i, l );
                return(
                <Panel header={x.name} key={x.id}  
               style={{ backgroundColor: 'darkgray' }} >
                <p>  {x?.products?.length > 0 ? 
               <p> Products: <strong> 
               { x.products.map(x => x.name).join(', ') } 
               </strong> </p> : 'No Product Available'} </p> 
                {
                    x?.children?.length > 0 ? 
                    childFunc(x.children)
                    :
                    null
                }
                </Panel>
                )
                })
            }

            
        </Collapse>

        </div>
    )
}

export default ProductCatagoryHierarchy;
0 users liked answer #2dislike answer #20
Sharif Himu profile pic
Sharif Himu

This is how I have done it

interface IObjectKeys {
[key: number]: boolean;

}

const [getCollapse, setCollapse] = React.useState<IObjectKeys>({
    0: false,
    1: false,
    2: false,
    3: false,
    4: false,
    5: false,
    6: false
});



const handleCollapse = (target: any)  => {
    setCollapse({ [target]: !getCollapse[target] });
}

And you can use like this

<Collapse in={getCollapse[1]} timeout="auto" unmountOnExit>
                            <ListItem>
                                <Typography>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Convallis convallis tellus id interdum velit</Typography>
                            </ListItem>
                        </Collapse>

<Collapse in={getCollapse[2]} timeout="auto" unmountOnExit>
                            <ListItem>
                                <Typography>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Convallis convallis tellus id interdum velit</Typography>
                            </ListItem>
                        </Collapse>
0 users liked answer #3dislike answer #30
Tikaram Mardi profile pic
Tikaram Mardi

Copyright © 2022 QueryThreads

All content on Query Threads is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).