I'm new to learning react and I'm trying to understand hooks but keep coming unstuck with the Rules of Hooks.
I have an app that I'm building where it has a navigation bar down the left hand side with icons that open a modal when clicked on:
I'm now trying to set up the 'modal wrapper' to contain each of the modals using this codesandbox as an example (from this stack overflow answer), however I'm running into issues with the Rules of Hooks and coming unstuck.
I've been reading up about reach hooks in the documentation here and understand the theory of only calling hooks at the top level and in functions but as there are quite a few hooks in my code, I'm getting confused between which hooks should be called where and how to then link back to them.
I'm also a bit confused by the uses of classes
vs. functions
as the example I'm trying to follow makes us of a class and I'm used to coding using functions.
My current code is below:
import React, { Component } from 'react'
import { Router, Link } from "react-router-dom";
import Modal from "react-modal";
import { createBrowserHistory } from 'history'
import { useAuth0 } from "@auth0/auth0-react";
import LogoutButton from "../Logout-Button";
import LoginButton from "../Login-Button";
import { Nav } from "react-bootstrap";
import Auth0ProviderWithHistory from '../../auth0-provider-with-history'
import UserSettings from "./UserSettings";
import Dashboard from "./Dashboard/Dashboard";
import MEMsLine from "./MemsLine";
import MEMsGrid from "./MemsGrid";
import Copyright from "../Copyright";
import clsx from 'clsx'
import { makeStyles } from '@material-ui/core/styles'
import {
CssBaseline,
Drawer,
Box,
AppBar,
Toolbar,
List,
Divider,
IconButton,
Container,
ListItem,
ListItemText,
ListItemIcon,
} from '@material-ui/core'
import {
ArrowBackIos as ArrowBackIcon,
Menu as MenuIcon,
Dashboard as DashboardIcon,
History as MEMsIcon,
People as PeopleIcon,
Place as PlaceIcon,
Cake as EventIcon,
LibraryMusic as MusicIcon,
Tv as TVIcon,
LocalMovies as MovieIcon,
SportsEsports as GameIcon,
Timeline as MEMslineIcon,
Settings as SettingsIcon,
} from '@material-ui/icons'
const history = createBrowserHistory();
const drawerWidth = 200
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
toolbar: {
paddingRight: 24, // keep right padding when drawer closed
},
toolbarIcon: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
menuButtonHidden: {
display: 'none',
},
title: {
flexGrow: 1,
},
drawerPaper: {
position: 'relative',
whiteSpace: 'nowrap',
backgroundColor: 'black',
color: 'white',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
paddingLeft: '8px',
width: theme.spacing(7),
[theme.breakpoints.up('sm')]: {
width: theme.spacing(9),
},
},
appBarSpacer: theme.mixins.toolbar,
content: {
flexGrow: 1,
height: '100vh',
overflow: 'auto',
},
container: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
},
paper: {
padding: theme.spacing(2),
display: 'flex',
overflow: 'auto',
flexDirection: 'column',
},
fixedHeight: {
height: 240,
},
navLink: {
textDecoration: 'none',
color: 'inherit',
},
appBarImage: {
maxHeight: '75px',
marginLeft: '-40px',
paddingRight: '20px',
},
personIcon: {
background: '#000000',
display: 'flex',
flexDirection: 'row',
},
}))
class UserAccount extends Component {
state = {
loginOpened: false,
signupOpened: false
};
openModal = modalType => () => {
if (modalType === "login") {
this.setState({
loginOpened: true,
signupOpened: false
});
} else if (modalType === "signup") {
this.setState({
loginOpened: false,
signupOpened: true
});
}
};
closeModal = modalType => () => {
if (modalType === "login") {
this.setState({
loginOpened: false
});
} else if (modalType === "signup") {
this.setState({
signupOpened: false
});
}
};
render() {
const { loginOpened, signupOpened } = this.state;
const classes = useStyles()
const [open, setOpen] = React.useState(false)
const handleDrawerOpen = () => {
setOpen(true)
}
const handleDrawerClose = () => {
setOpen(false)
}
const AuthNav = () => {
const { isAuthenticated } = useAuth0();
return (
<Nav className="justify-content-end">
{isAuthenticated ? <LogoutButton /> : <LoginButton />}
</Nav>
);
};
return (
// <AuthConsumer>
// {({ user }) => (
// <Can
// role={user.role}
// perform="useraccount:visit"
// yes={() => (
<Router history={history}>
<Auth0ProviderWithHistory>
<div className={classes.root}>
<CssBaseline />
<AppBar style={{ background: '#000000' }}
position="absolute"
className={clsx(classes.appBar, open && classes.appBarShift)}
>
<Toolbar className={classes.toolbar}>
<IconButton
edge="start"
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
className={clsx(
classes.menuButton,
open && classes.menuButtonHidden
)}
>
<MenuIcon />
</IconButton>
<Link to="/" className={classes.navLink}>
<img
className={classes.appBarImage}
src='https://storage.googleapis.com/mems-images/mems-logo-small-rounded.png'
alt="mems logo"
/>
</Link>
<div className={classes.personIcon} style={{ width: '100%', justifyContent: 'flex-end' }}>
<AuthNav />
</div>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
classes={{
paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose),
}}
open={open}
>
<div className={classes.toolbarIcon}>
<IconButton onClick={handleDrawerClose}>
<ArrowBackIcon style={{ color: 'white' }} />
</IconButton>
</div>
<Divider />
<List>
<ListItem button>
<ListItemIcon>
<SettingsIcon style={{ color: 'white' }} />
<UserSettings />
</ListItemIcon>
<ListItemText primary="Settings" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DashboardIcon style={{ color: 'white' }} />
<Dashboard />
</ListItemIcon>
<ListItemText primary="Dashboard" />
</ListItem>
<ListItem button>
<ListItemIcon>
<MEMslineIcon style={{ color: 'white' }} />
<MEMsLine />
</ListItemIcon>
<ListItemText primary="MEMsLine" />
</ListItem>
<ListItem button>
<ListItemIcon>
<MEMsIcon style={{ color: 'white' }} />
<MEMsGrid />
</ListItemIcon>
<ListItemText primary="All MEMs" />
</ListItem>
<ListItem button>
<ListItemIcon>
<EventIcon style={{ color: 'white' }} />
<p>Events</p>
</ListItemIcon>
<ListItemText primary="Events" />
</ListItem>
<ListItem button>
<ListItemIcon>
<PeopleIcon style={{ color: 'white' }} />
<p>People</p>
</ListItemIcon>
<ListItemText primary="People" />
</ListItem>
<ListItem button>
<ListItemIcon>
<PlaceIcon style={{ color: 'white' }} />
<p>Places</p>
</ListItemIcon>
<ListItemText primary="Places" />
</ListItem>
<ListItem button>
<ListItemIcon>
<MusicIcon style={{ color: 'white' }} />
<p>Music</p>
</ListItemIcon>
<ListItemText primary="Music" />
</ListItem>
<ListItem button>
<ListItemIcon>
<MovieIcon style={{ color: 'white' }} />
<p>Movies</p>
</ListItemIcon>
<ListItemText primary="Movies" />
</ListItem>
<ListItem button>
<ListItemIcon>
<TVIcon style={{ color: 'white' }} />
<p>TV Shows</p>
</ListItemIcon>
<ListItemText primary="TV Shows" />
</ListItem>
<ListItem button>
<ListItemIcon>
<GameIcon style={{ color: 'white' }} />
<p>Games</p>
</ListItemIcon>
<ListItemText primary="Games" />
</ListItem>
</List>
<Divider />
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Container maxWidth="lg" className={classes.container}>
<Modal isOpen={loginOpened} onRequestClose={this.closeModal("login")}>
<h1>Login</h1>
<button onClick={this.openModal("signup")}>Open Signup</button>
<button onClick={this.closeModal("login")}>Close this modal</button>
</Modal>
<Modal isOpen={signupOpened} onRequestClose={this.closeModal("signup")}>
<h1>Sign Up</h1>
<button onClick={this.openModal("login")}>Open Login</button>
<button onClick={this.closeModal("signup")}>Close this modal</button>
</Modal>
<button onClick={this.openModal("login")}>Open Login</button>
<button onClick={this.openModal("signup")}>Open Signup</button>
<Box pt={4}>
<Copyright />
</Box>
</Container>
</main>
</div>
</Auth0ProviderWithHistory>
</Router >
// )}
// no={() => <Redirect to="/" />}
// />
// )}
// </AuthConsumer>
);
}
}
export default UserAccount
Any pointers very welcome!
toggle = () =>{}
not toggle = () => () =>{}
<- as this would mean you're returning a function instead of just opening the function body(learn more about arrow functions) class ClassComponent extends React.Component {
state = {
show: false
};
toggle = () => {
const { show } = this.state;
this.setState({ show: !show });
};
render() {
const { show } = this.state;
return (
<div>
<span>{show ? "Show Class" : "Hide Class"}</span>
<button onClick={this.toggle}>Click here</button>
</div>
);
}
}
function FunctionComponent(props) {
const [show, setShow] = React.useState(false);
const toggle = () => setShow(!show);
return (
<div>
<span>{show ? "Show Function" : "Hide Function"}</span>
<button onClick={toggle}>Click here</button>
</div>
);
}
Now after understanding this bit this is how you can fix your codebase:
this.closeModal("login")
you have to call it again like this.closeModal("login")()
then it will work.In summary, hooks can only be used in function components, they provide the same functionalities has class components have. You can set the state, listed to some lifecycle methods, and much more. As function components used to be used in the past just for rendering and probably do some simple logic now they can be used the same way a class component would.
Link the code sandbox with a working example is here.