listitem onclick material ui giving an error - reactjs

Hi I am trying to write navigation list like below.
Codepen Link: https://9huof.csb.app/
https://codesandbox.io/s/nervous-water-9huof?file=/src/index.js
const NavList = () => {
const history = useHistory();
return (
<div>
<ListItem button onClick={() => history.push("/reports")}>
<ListItemIcon>
<DashboardIcon />
</ListItemIcon>
<ListItemText primary='Reports' />
</ListItem>
<ListItem button onClick={() => history.push("/quiz")}>
<ListItemIcon>
<PeopleIcon />
</ListItemIcon>
<ListItemText primary='Quiz' />
</ListItem>
</div>
);
};
Where ListItem called inside List from the functional component like below.
<Drawer variant='permanent' open={open}>
<Toolbar
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
px: [1],
}}
>
<IconButton onClick={toggleDrawer}>
<ChevronLeftIcon />
</IconButton>
</Toolbar>
<Divider />
<List>
<NavList />
</List>
</Drawer>
When I click on quiz it says "Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:"

The problem is with the render props of your Route components. Use an inline function as seen here. So you need to change your Routes from this:
<AuthRoute path="/quiz" render={Quiz} />
to this:
<AuthRoute path="/quiz" render={() => <Quiz />} />

Instead of render in your route change the prop to component like so
<AuthRoute path="/quiz" component={Quiz} />

Related

How to remove the paddingRight when a MUI Drawer is opened

My problem its quite simple but i not know how to solve that, when the Drawer from MaterialUI is opened are added some css into the body in my page, and one of the css add a padding-right and that are screwing the page indentation.
Drawer closed:
Drawer opened:
The problem:
Code:
<React.Fragment key={"menu-button"} >
<MenuIcon onClick={toggleDrawer(true)} />
<Drawer
anchor={"left"}
open={state}
onClose={toggleDrawer(false)}
>
<Box
sx={{ width: 250, height: '100%'}}
className={styles.background}
role="presentation"
onClick={toggleDrawer(false)}
onKeyDown={toggleDrawer(false)}
>
<List>
{['About me', 'Projects', 'Experience', 'Education'].map((text, index) => (
<ListItem key={text} disablePadding>
<ListItemButton className={styles.option}>
<ListItemIcon className={styles.optionIcon}>
{icons[index]}
</ListItemIcon>
<ListItemText primary={text} />
</ListItemButton>
</ListItem>
))}
</List>
</Box>
</Drawer>
</React.Fragment>
The styles are adding only colors.
I fixed adding "disableScrollLock={ true }" into the Drawer component :)

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

How would I render my MUI drawer on all components except login page?

I am trying to render the Material UI drawer on all pages except the login page but it's not working accordingly.
In short
My App component has 2 routes login and dashboard (Both Working)
Then my dashboard page has multiple routes for home and about pages (None of them working.)
Below is my code separately. Also, I'm attaching codeSandBox link for the same
Expected Output To understand my desired output
On login URL = <Login/> should render
On dashboard URL = <AppDrawer/> should render
inside AppDrawer I am having more routes in
<main>
<Route path="/home" component={home}/>
Route path="/about" component={about}/>
</main>
My actual code is here:
App.js
import { Switch, Route } from "react-router-dom";
import AppDrawerBar from "./compponents/AppDrawerBar";
import Login from "./pages/Login";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/login" exact component={Login} />
<Route path="/dashboard" component={AppDrawerBar} />
</Switch>
</div>
);
}
Login.js
import React from "react";
const Login = () => {
return <h1>Login Page</h1>;
};
export default Login;
AppDrawerBar.js
import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core";
import Drawer from "#material-ui/core/Drawer";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import List from "#material-ui/core/List";
import Typography from "#material-ui/core/Typography";
import Divider from "#material-ui/core/Divider";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import ListItemText from "#material-ui/core/ListItemText";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import Home from "../pages/Home";
import About from "../pages/About";
import { Route, Switch } from "react-router-dom";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
appBar: {
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
appBarShift: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
})
},
menuButton: {
marginRight: theme.spacing(2)
},
hide: {
display: "none"
},
drawer: {
width: drawerWidth,
flexShrink: 0
},
drawerPaper: {
width: drawerWidth
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end"
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: -drawerWidth
},
contentShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
}
}));
export default function PersistentDrawerLeft() {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{["All mail", "Trash", "Spam"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
// More Routes for main pages
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</main>
</div>
);
}
Home.js
import React from "react";
const Home = () => {
return <h1>Home Page</h1>;
};
export default Home;
About.js
import React from "react";
const About = () => {
return <h1>About Page</h1>;
};
export default About;
You are mainly missing two steps to get your router working and updating your main content:
Propagate route change upon items click: The side Drawer component should be wrapped with a Router component and individual ListItems should be declared with Link as their component so whenever an item is clicked on the side menu, the change is propagated.
Updating your main content based on the navigated route: The main element should make use only of Route components to have the content dynamically loaded upon URL location change.
Here down the updated AppDrawerBar component (note that I omitted the redundant ListItems to bring focus to the important changes:
return (
<BrowserRouter>
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
<ListItem button key="home" to="/home" component={Link}>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
</List>
<Divider />
<List>
<ListItem button key="about" to="/about" component={Link}>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="About" />
</ListItem>
</List>
</Drawer>
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</main>
</div>
</BrowserRouter>
);
Edit:
The Link component mentioned above is the one from react-router-dom since it will redirect to the new /home and /about locations allowing the main directive content to update accordingly.
Here you can find a forked (working) copy of your AppDrawerBar component.
I saw you use react-router so if you want your drawer is displayed all the time except on the login page you can simply add conditional render for your drawer using the useHistory hook from "react-router-dom".
const history = useHistory
console.log(history.location.pathname) // give the current path name
So here is a solution :
export default function PersistentDrawerLeft() {
const history = useHistory(); // from react-router-dom
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<CssBaseline />
{history.location.pathname !== "/login" &&
<>
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, open && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
</div>
<Divider />
<List>
{["Inbox", "Starred", "Send email", "Drafts"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{["All mail", "Trash", "Spam"].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
</>
}
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
// More Routes for main pages
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/about" component={About} />
</Switch>
</main>
</div>
);
}

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