Hyokune
Hyokune

Reputation: 227

React Material UI Multiple Collapse

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>

Upvotes: 0

Views: 36068

Answers (4)

alphacat
alphacat

Reputation: 69

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>

Upvotes: 0

Sharif Himu
Sharif Himu

Reputation: 126

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;

Upvotes: 0

Md Abdul Halim Rafi
Md Abdul Halim Rafi

Reputation: 1970

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>
  )
}

Upvotes: 9

Ricardo Costa
Ricardo Costa

Reputation: 729

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 });
    }

Upvotes: 12

Related Questions