How to open a selected nested list on Material-UI? - reactjs

I have a code in React where I have two list menus that have nested menus.
class Nav extends React.Component {
state = { open: false };
handleClick = () => {
this.setState({ open: !this.state.open });
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<List
component="nav"
>
<ListItem button onClick={this.handleClick}>
<ListItemText primary="Files" />
{this.state.open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemText primary="Providers" />
</ListItem>
<ListItem button className={classes.nested}>
<ListItemText primary="Insurance Companies" />
</ListItem>
</List>
</Collapse>
<ListItem button onClick={this.handleClick}>
<ListItemText primary="Utilities" />
{this.state.open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemText primary="Excel Templates" />
</ListItem>
<ListItem button className={classes.nested}>
<ListItemText primary="Upload File" />
</ListItem>
</List>
</Collapse>
</List>
</div>
);
}
}
When I click the first menu, the second menu also opens. How do I only open one menu and the other one remains closed?

I think you will need to have two seperate values in state in order to only open one at a time
state = { openMenu1: false, openMenu2: false }
And the click events should trigger only one of those to go to true/false.
Make sure to change the variable in the Collapse component to use the corresponding state as well

Related

React material ui nested list collapse does not work

I have a nested data. When I press parent data, I want subdata to appear under it. My code is as below.
<List>
{data1.map((task, index)=>(
<ListItem>
<ListItemButton onClick={() => handleClick(task.name)}>
<ListItemText key={index} primary={task.name}/>
</ListItemButton>
{task.subdata.map((child, i)=>(
<Collapse unmountOnExit in={open === task.name}>
<List>
<ListItemButton >
<ListItemText primary={child.name}/>
</ListItemButton>
</List>
</Collapse>
))}
</ListItem>
))}
</List>
screenshot is as below.

When i click one button its open all buttons simultaneously

When I click on the post button it opens all buttons like media and user .i tried to define state function but when i click one button all buttons are open simultaneously.
this problem i am facing
My code are here
export default function ListItem() {
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
return (
<div>
<List
sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
component="nav"
aria-labelledby="nested-list-subheader"
>
{/* dashbord */}
<ListItemButton component="a" href="#simple-list">
<ListItemIcon>
<DashboardTwoToneIcon />
</ListItemIcon>
<ListItemText primary="DashBoard" />
</ListItemButton>
{/* post */}
<ListItemButton onClick={handleClick}>
<ListItemIcon>
<PostAddTwoToneIcon />
</ListItemIcon>
<ListItemText primary="Post" />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="New post" component="a" />
</ListItemButton>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="All posts" />
</ListItemButton>
</List>
</Collapse>
{/* Media */}
<ListItemButton onClick={handleClick}>
<ListItemIcon>
<SubscriptionsTwoToneIcon />
</ListItemIcon>
<ListItemText primary="Media" />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="All Media" component="a" />
</ListItemButton>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="Add New Media" />
</ListItemButton>
</List>
</Collapse>
You are using a single boolean state for all of them. I suggest storing the open status of each item/element you want to toggle in an object and check if the specific item/element is currently toggled open.
const [open, setOpen] = React.useState({});
const handleClick = (id) => () => {
setOpen(open => ({
...open,
[id]: !open[id],
}));
};
...
<ListItemButton onClick={handleClick("post")}> // <-- pass id
<ListItemIcon>
<PostAddTwoToneIcon />
</ListItemIcon>
<ListItemText primary="Post" />
{open["post"] ? <ExpandLess /> : <ExpandMore />} // <-- check by id
</ListItemButton>
<Collapse
in={open["post"]} // <-- check by id
timeout="auto"
unmountOnExit
>
<List component="div" disablePadding>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="New post" component="a" />
</ListItemButton>
<ListItemButton sx={{ pl: 4 }} component="a" href="#">
<ListItemText primary="All posts" />
</ListItemButton>
</List>
</Collapse>
Apply same for other buttons and collapsables but using specific unique keys.
You call function setOpen on a click on any object, however setOpen seems to change the state of a global variable that is used to determine whether the contents should be shown or not. Try to set a unique variable as open state for every button and only change this variable with a function in onClick

Creating Routes in React

I created my drawer using Materials UI and now I can't create links to other pages.I tried to set the link to https://www.google.com/ also.But it also didn't work.
Below is the part of my code.
<List
component="nav"
aria-labelledby="nested-list-subheader"
className={classes.menu}
>
<ListItem button>
<ListItemText primary="Buses" component={Link} to="/buses" />
</ListItem>
<ListItem button>
<ListItemText primary="Condutors" />
</ListItem>
<ListItem button onClick={handleClick}>
<ListItemText primary="Reports" />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemText primary="Generate Report" />
</ListItem>
<ListItem button className={classes.nested}>
<ListItemText primary="View Report" />
</ListItem>
<ListItem button>
<ListItemText primary="Profle" />
</ListItem>
<ListItem button>
<ListItemText primary="Log Out" />
</ListItem>
</List>
</Collapse>
</List>```
You can use Link for react-router-dom its like a in html
or if Is for page in tour web, you can use useHistory the same module
From react-router-dom documentation:
Provides declarative, accessible navigation around your application.
You can use the normal <a><a/> HTML tag for referring to external links.

The default "inset" property padding. ListItemText Component

I'm styling a nested list in a React app using material-ui. The default "inset" property padding is to large (56px) so I'm trying to override that default value. But I can't.
any suggestions? Thank you !!
<Fragment>
<ListItem button onClick={this.handleClick}>
<ListItemText
primary="Zones"
primaryTypographyProps={{ variant: 'subtitle2' }}
/>
{this.state.open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button
component={Link}
to={`${this.props.match.url}/zone/all-zones`}
onClick={THIS.handleMenuOpen}
>
<ListItemText
inset
primary="Show all"
primaryTypographyProps={{ variant: 'subtitle2' }}
/>
</ListItem>
</List>
</Collapse>
</Fragment>
The purpose of the inset prop on ListItemText is to align text of items that don't have an icon with the text of items that do (see the Inset List demo). It is not for indenting nested list items.
If you look at the Nested List demo, you will find that the indent is done via paddingLeft applied to the nested list items. In the demo, this is set to 32px (theme.spacing(4)), but you can set this to whatever you want it to be.
Here is the code from the Nested List demo:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import ListSubheader from "#material-ui/core/ListSubheader";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import Collapse from "#material-ui/core/Collapse";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import DraftsIcon from "#material-ui/icons/Drafts";
import SendIcon from "#material-ui/icons/Send";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import StarBorder from "#material-ui/icons/StarBorder";
const useStyles = makeStyles(theme => ({
root: {
width: "100%",
maxWidth: 360,
backgroundColor: theme.palette.background.paper
},
nested: {
paddingLeft: theme.spacing(4)
}
}));
export default function NestedList() {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
const handleClick = () => {
setOpen(!open);
};
return (
<List
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
Nested List Items
</ListSubheader>
}
className={classes.root}
>
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText primary="Sent mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItem>
<ListItem button onClick={handleClick}>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText primary="Starred" />
</ListItem>
</List>
</Collapse>
</List>
);
}
Group-Menu
<List component="nav" aria-label="patient-group">
<ListItem>
<ListItemText
primary="patients"
primaryTypographyProps={{variant:'button' }}
/>
</ListItem>
<PatientMenu />
<Divider />
<ListItem>
<ListItemText
primary="delivery sheets"
primaryTypographyProps={{ variant: 'button' }}
/>
</ListItem>
<DeliverySheetMenu />
</List>
Patient-Menu
<Fragment>
<List component="div" disablePadding>
<ListItem button
component={Link}
to={`${this.props.match.url}/patient/all_patients`}
onClick={this.handleOnClick};
>
<ListItemText
className='nested'
style={{paddingLeft: 20 }}
primary="Show all"
primaryTypographyProps={{variant:'subtitle2'}}
/>
</ListItem>
</List>
</Fragment>

Render component in Drawer with Material-ui Lists

This feels like it should be self-explanatory in the Docs, but they abstract out a key part of the List code to another file and don't show it. So, I'm left asking you fine folks.
I'm looking to do something pretty simple: Use a List-based sidebar with Material-ui (beta-version) to render a component in a Drawer. I have the list setup, but I can't figure out how to make the ListItems link to the components and perform the render. Here's my code:
class DashboardScreen extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={this.props.classes.container}>
<Appbar pagetitle="Dashboard" />
<div className={this.props.classes.appFrame}>
<Drawer
type="permanent"
classes={{
paper: this.props.classes.drawerPaper,
}}
anchor="left">
<div className={this.props.classes.drawerHeader} />
<List>
<ListItem button>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<ListItemText primary="Analytics"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<ImportContactsIcon />
</ListItemIcon>
<ListItemText primary="Create Investigation"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<ListItemText primary="Create Survey"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<AssignmentTurnedInIcon />
</ListItemIcon>
<ListItemText primary="Create Response"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary="Settings"/>
</ListItem>
</List>
</Drawer>
<main className={this.props.classes.content}>
Testing Stuff!
</main>
</div>
</div>
)
}
}
What I'm rendering is just another separate component called "Analytics". There's nothing special about it. How do I get the first ListItem, with text = "Analytics", render said component <Analytics />?
The ListItem will have an onClick property that it propagates through to the appropriate child components. So, if you're searching to receive clicks for each ListItem, you can simply do this:
<ListItem button onClick={() => alert("Hey buddy this will alert when clicked.")}>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<Analytics />
</ListItem>
<ListItem button onClick={() => alert("This is a click on a different item.")}>
<ListItemIcon>
<ImportContactsIcon />
</ListItemIcon>
<Analytics />
</ListItem>
The propagation of props down to the appropriate area is described at the bottom of the props section on the ListItem API page.
Solved!
Here's my code:
class DashboardScreen extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className={this.props.classes.container}>
<Appbar pagetitle="Dashboard" />
<div className={this.props.classes.appFrame}>
<Drawer
type="permanent"
classes={{
paper: this.props.classes.drawerPaper,
}}
anchor="left">
<div className={this.props.classes.drawerHeader} />
<List>
<ListItem button component={({...props}) => <Link to={this.props.match.url + '/analytics'} {...props} />}>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<ListItemText primary="Analytics"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<ImportContactsIcon />
</ListItemIcon>
<ListItemText primary="Create Investigation"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<AssignmentIcon />
</ListItemIcon>
<ListItemText primary="Create Survey"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<AssignmentTurnedInIcon />
</ListItemIcon>
<ListItemText primary="Create Response"/>
</ListItem>
<ListItem button>
<ListItemIcon>
<SettingsIcon />
</ListItemIcon>
<ListItemText primary="Settings"/>
</ListItem>
</List>
</Drawer>
<main className={this.props.classes.content}>
<Route path={this.props.match.url + "/analytics"} component={Analytics}/>
</main>
</div>
</div>
)
}
}
Essentially, you need to contain the Route for the component within the main section of the Drawer. Then, you need to Link to that route via the component wrapped within the component prop on the ListItem.
Hope this helps somebody!
Did you try to simply change ListItemText with your custom component?
<ListItem button>
<ListItemIcon>
<AssessmentIcon />
</ListItemIcon>
<Analytics />
</ListItem>

Resources