Reputation: 227
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
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
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
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
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