I want to create a persistent drawer using the material-ui library on react. I have created a partial navbar and i was testing if i could route to other pages if i click the buttons. The address routes properly if i click on the buttons, however, i am unable to see the content of those pages. This is my first time working with react in general, so i am not sure where im going wrong and i would really appreciate some assistance.
This is the code i have so far:
App.js:
function App() {
return (
<div>
<Box sx={{display: 'flex'}}>
<NavBar>
<Routes>
<Route exact path='/' element={<Navigate to='/home' />} />
<Route exact path='/home' element={<HomePage />} />
<Route exact path='/clients' element={<ClientsPage />} />
<Route exact path='/resources' element={<ResourcesPage />} />
<Route exact path='/projects' element={<ProjectPage />} />
</Routes>
</NavBar>
</Box>
</div>
)
}
export default App;
index.js:
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
NavBar.js:
const drawerWidth = 240;
const useStyles = makeStyles({
drawer: {
width: drawerWidth
},
drawerPaper: {
width: drawerWidth
},
root: {
display: 'flex'
}
})
export const NavBar = () => {
const classes = useStyles()
const navigate = useNavigate()
const [open, setOpen] = useState(true)
const menuItems = [
{
text: 'Home',
icon: <Home sx={{ color: orange[500] }} />,
path: '/home',
component: <HomePage />
},
{
text: 'Projects',
icon: <Work sx={{ color: orange[500] }} />,
path: '/projects',
component: <ProjectPage />
},
{
text: 'Clients',
icon: <People sx={{ color: orange[500] }} />,
path: '/clients',
component: <ClientsPage />
},
{
text: 'Resources',
icon: <Settings sx={{ color: orange[500] }} />,
path: '/resources',
component: <ResourcesPage />
}
]
return (
<>
<Grid container>
<div className={classes.root}>
<Drawer className={classes.drawer} variant="persistent" anchor="left" open={open} classes={{paper: classes.drawerPaper}}>
<div>
<Typography variant="h5">
Navigation
</Typography>
<List>
{menuItems.map(item => (
<ListItem key={item.text} button onClick={() => navigate(item.path)}>
<ListItemIcon> {item.icon} </ListItemIcon>
<ListItemText primary={item.text} />
</ListItem>
))}
</List>
</div>
</Drawer>
</div>
</Grid>
</>
)
}
The HomePage, and all the other pages being called is just a header tag which says Hello . I cannot see the Hello when i click on the buttons. Thank you guys for the help!
After looking at my code for a while, i figured what the error was. The Navbar was routing to the page, which was right, but i was not displaying the contents of the page. The great part was I was unconsciously wrapping my router and all the routes in NavBar component, so all i had to do was pass a prop called children like this:
export const NavBar = ({children}) => {
//code here
}
and then after i finish with the drawer, just add a small div where i display the children. So it would look like this:
</Drawer>
<div>
{children}
</div
The UI is still a bit messy, but the content of the pages are being shown.
Related
my task is to want a nested route in the access/ route means I have a parent route access/ so I need a nested in this route like /access/add-team this nested I want to do in one click of a button mean I'm my access/ route component I have I one button called Add Team when someone clicks on that button I am pushing to that user on this /access/add-team route so the route is getting change based on click but my add team component is net getting render what I am missing I am not sure I have added that every this in Layout.js file my component are present in Layout.js let me know what I need to add to work fine this also I added complete code link bellow
AppRoutes.js
const Layout = lazy(() => import("./Layout"));
const PageNotFound = lazy(() => import("./PageNotFound"));
const isLoggedIn = true;
const PrivateRoute = ({ component: Component, isLoggedIn }) => {
return (
<Route
render={(props) =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
};
export const AppRoutes = () => {
return (
<HashRouter>
<React.Suspense fallback={""}>
<Switch>
<PrivateRoute path="/" isLoggedIn={isLoggedIn} component={Layout} />
<Route
path="*"
name="Not Found"
render={(props) => <PageNotFound {...props} />}
/>
</Switch>
</React.Suspense>
</HashRouter>
);
};
function Layout(props) {
const history = useHistory();
const { window } = props;
const [mobileOpen, setMobileOpen] = React.useState(false);
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
const drawer = (
<div>
<Toolbar />
<Divider />
<List sx={{ minWidth: 230 }}>
{newText
?.filter((data) => data.permission)
?.map((value, index) => (
<ListItemButton
key={index}
sx={{ pt: 1, pb: 1, mt: 3.5 }}
onClick={() => history.push(value.route)}
>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary={value.label} />
</ListItemButton>
))}
</List>
<Divider />
</div>
);
const container =
window !== undefined ? () => window().document.body : undefined;
return (
<Box sx={{ display: "flex" }}>
<CssBaseline />
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` }
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: "none" } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
Responsive drawer
</Typography>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label="mailbox folders"
>
<Drawer
variant="permanent"
sx={{
display: { xs: "none", sm: "block" },
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: drawerWidth
}
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` }
}}
>
<Toolbar />
<Suspense fallback={""}>
<Switch>
{ROUTES.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
name={route.name}
render={(props) => <route.component {...props} />}
/>
) : null;
})}
<Redirect exact path="/" to="access" />
<Route
path="*"
name="Not Found"
render={(props) => <PageNotFound {...props} />}
/>
</Switch>
</Suspense>
</Box>
</Box>
);
}
Within the Switch component path order and specificity matters! You want to order the routes from more specific paths to less specific paths. In this case you are rendering the "/access" path prior to any of the sub-route "/access/***" paths, so it is matched and rendered instead of the one really matching the path in the URL.
To fix, move the "/access" route config below the more specific routes.
export const ROUTES = [
// move "/access" route from here
{
name: "addTeam",
path: "/access/add-team",
component: lazy(() => import("./AddTeam"))
},
{
name: "addUser",
path: "/access/add-user",
component: lazy(() => import("./AddUser"))
},
// to here
{
name: "access",
path: "/access",
component: lazy(() => import("./Access"))
},
{
name: "admin",
path: "/admin",
component: lazy(() => import("./Admin"))
}
];
I'm trying to build a dark mode toggle in JS using React.
I'm using Material-UI to design my page and I've managed to make the button change the theme of my whole page when clicked. My issue is that whenever I refresh or change the Routes, the button state doesn't get carried over. Is there anything I'm missing?
This is the code for my button:
export default function ThemeSwitch({ toggleDark, settoggleDark }) {
const handleModeChange = () => {
settoggleDark(!toggleDark);
};
return (
<div className="theme-btn">
<IOSSwitch
checked={toggleDark}
onChange={handleModeChange}
name="toggleDark"
color="default"
/>
</div>
);
}
And this is my App.js:
function App() {
const [toggleDark, settoggleDark] = useState(false);
const myTheme = createTheme({
palette: {
mode: toggleDark ? "dark" : "light",
primary: {
main: "#ff1744",
},
secondary: {
main: "#880e4f",
},
},
});
return (
<Router>
<ThemeProvider theme={myTheme}>
<CssBaseline />
<Navbar />
<ThemeSwitch
toggleDark={toggleDark}
settoggleDark={settoggleDark}
className="theme-btn"
/>
<UserAuthContextProvider>
<Routes>
<Route
path="/"
element={
<ProtectedRoute>
<Hero />
</ProtectedRoute>
}
/>
<Route path="signup" element={<SignUp />} />
<Route path="signin" element={<SignIn />} />
</Routes>
</UserAuthContextProvider>
</ThemeProvider>
</Router>
);
}
In order to maintain the value, you can use localStorage instead of react state.
Whenever you set the state save it in local storge like:
localStorage.setItem("toggleDark", true);
And get the state by using
localStorage.getItem("toggleDark");
I have written below routes in App.js -
function App() {
return (
<>
<BrowserRouter>
<Switch>
<Route path="/" exact component={Dashboard} ></Route>
<Route path="/details/:index" exact component={ItemDetails} ></Route>
<Dashboard></Dashboard>
</Switch>
</BrowserRouter>
</>
);
}
export default App;
I have another component - Items which has Card (Reactstrap). Each card is having a Link -
function Items(props) {
console.log(props.index.index)
return (
<Link to={{pathname:"/details",param1:props.index.index}}>
<Card tag="a" key={props.index.index} style={{display:'flex',width:'25%',flexWrap:'nowrap',float:'left',cursor: "pointer"}}
>
<CardBody>
<CardTitle> {props.card.card.value} </CardTitle>
</CardBody>
</Card>
</Link>
)
}
export default Items
Within Link tag , to attribute , I have mentioned -
to={{pathname:"/details",param1:props.index.index}}
By this I am expecting , upon clicking of card , component - ItemDetails should get rendered.
But I cannot see , ItemDetails has got rendered.
Do I need to add anything else within my current code ?
You can use the useHistory hook which solve this problem easily
import React from 'react'
import {useHistory} from 'react-router-dom'
function Items({index, card}) {
const history = useHistory()
function navigateTo(){
history.push(`/details/${index.index}`)
}
return (
<Card onClick={navigateTo} tag="a" key={props.index.index} style={{display:'flex',width:'25%',flexWrap:'nowrap',float:'left',cursor: "pointer"}}
>
<CardBody>
<CardTitle> {card.card.value} </CardTitle>
</CardBody>
</Card>
)
}
export default Items
You should add exact attribute to your "/" route. And component is given in Route not in Switch block
function App() {
return (
<>
<BrowserRouter>
<Switch>
<Route exact path="/" exact component={Dashboard} />
<Route path="/details/:index" exact component={ItemDetails} />
</Switch>
</BrowserRouter>
</>
);
}
export default App;
You may change the path param with Template literals
<Link to={`/details/${param1:props.index.index}`}></Link>
Complete code is something like this
function Items(props) {
return (
<Link to={`/details/${param1:props.index.index}`}>
<Card tag="a" key={props.index.index} style={{display:'flex',width:'25%',flexWrap:'nowrap',float:'left',cursor: "pointer"}}
>
<CardBody>
<CardTitle> {props.card.card.value} </CardTitle>
</CardBody>
</Card>
</Link>
)
}
export default Items
Use NavLink and change the Card tag from a to something else. Otherwise you will have nested a tags.
import { NavLink } from 'react-router-dom'
function Items(props) {
const linkTo = `/details/${props.index.index}`;
return (
<NavLink to={linkTo}>
<Card tag="span" key={props.index.index} style={{display:'flex',width:'25%',flexWrap:'nowrap',float:'left',cursor: "pointer"}}
>
<CardBody>
<CardTitle> {props.card.card.value} </CardTitle>
</CardBody>
</Card>
</NavLink>
)
}
I'm a little confused on how to set up a theme wrapped around my index.js components, while using a switch state in my header.js that triggers light or dark theme.
trying to streamline this as much as possible to require less coding. However I'm a React Noob and would like some guidance on how I can set up my code properly to get everything working.
EDIT: Thanks to Diyorbek, I've managed to understand how to get this working. I'm now getting a theme is not defined for my index.js file
index.js:
import { ThemeProvider, CssBaseline } from "#material-ui/core";
import { createMuiTheme } from "#material-ui/core";
const MyThemeContext = React.createContext({});
export function useMyThemeContext() {
return useContext(MyThemeContext);
}
function MyThemeProvider(props) {
const [isDarkMode, setIsDarkMode] = useState(false);
const theme = useMemo(
() =>
createMuiTheme({
palette: {
type: isDarkMode ? 'dark' : 'light',
},
}),
[isDarkMode]
);
return (
<ThemeProvider theme={theme}>
<MyThemeContext.Provider value={{ isDarkMode, setIsDarkMode }}>
{props.children}
</MyThemeContext.Provider>
</ThemeProvider>
);
}
const routing = (
<Router>
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<Header />
<Switch>
<Route exact path="/" component={App} />
</Switch>
<Footer />
</ThemeProvider>
</React.StrictMode>
</Router>
);
ReactDOM.render(routing, document.getElementById('root'));
header.js:
import { useMyThemeContext } from '../index';
const useStyles = makeStyles((theme) => ({
appBar: {
borderBottom: `1px solid ${theme.palette.divider}`,
},
link: {
margin: theme.spacing(1, 1.5),
},
toolbarTitle: {
flexGrow: 1,
},
}));
function Header() {
const classes = useStyles();
const [isDarkMode, setIsDarkMode] = useMyThemeContext();
return (
<React.Fragment>
<CssBaseline />
<AppBar
position="static"
color="default"
elevation={0}
className={classes.appBar}
>
<Toolbar className={classes.toolbar}>
<Switch
checked={isDarkMode}
onChange={() => setIsDarkMode(!isDarkMode)}
/>
</Toolbar>
</AppBar>
</React.Fragment>
);
}
export default Header;
Thank you in advance for the help :)
You should make use of React Context.
This is how I would approach this case:
const MyThemeContext = createContext({});
export function useMyThemeContext() {
return useContext(MyThemeContext);
}
function MyThemeProvider(props) {
const [isDarkMode, setIsDarkMode] = useState(false);
const theme = useMemo(
() =>
createMuiTheme({
palette: {
type: isDarkMode ? 'dark' : 'light',
},
}),
[isDarkMode]
);
return (
<ThemeProvider theme={theme}>
<MyThemeContext.Provider value={{ isDarkMode, setIsDarkMode }}>
{props.children}
</MyThemeContext.Provider>
</ThemeProvider>
);
}
const routing = (
<Router>
<React.StrictMode>
<MyThemeProvider>
<CssBaseline />
<Header />
<Switch>
<Route exact path="/" component={App} />
</Switch>
<Footer />
</MyThemeProvider>
</React.StrictMode>
</Router>
);
import { useMyThemeContext } from "...."
function Header() {
const classes = useStyles();
const {isDarkMode, setIsDarkMode} = useMyThemeContext();
return (
<React.Fragment>
<CssBaseline />
<AppBar
position="static"
color="default"
elevation={0}
className={classes.appBar}
>
<Toolbar className={classes.toolbar}>
<Switch
checked={isDarkMode}
onChange={() => setIsDarkMode(!isDarkMode)}
/>
</Toolbar>
</AppBar>
</React.Fragment>
);
}
When working with react, if you want to change global variables that act like states, you'll want to use a Context. This allows you to access data and render components based on changes to this data throughout every page and component. Consult the Context documentation for more information & guides on usage.
Im trying to use material Ui with my react frontend project. I have implemented the AppBar and drawer component and they are working just fine. This is what I have achieved till now :
export default function MiniDrawer() {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar style={{background: 'black'}}
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, {
[classes.hide]: open,
})}
>
<MenuIcon />
</IconButton>
<Typography className={classes.mystyle} variant="h5" >
CodeBasics
</Typography>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open,
}),
}}
open={open}
>
<div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</div>
<Divider />
<List>
{['Home', 'Algorithms', 'DataStructures'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
</Drawer>
<main className={classes.content}>
<div className={classes.toolbar} />
<Layout>
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/DataStructures" component={DataStructures} />
</Switch>
</Router>
</Layout>
</main>
</div>
);
}
Now I want to map each 'Home', 'DataStructures' and 'Algorithms' with a different route. I have achieved a way to a route the List component in this way.
<List>
{['Home', 'Algorithms', 'DataStructures'].map((text, index) => (
<ListItem button key={text} component="a" href="www.google.com">
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
but with its only getting me go to the same link for every item in the list. How do I map individual list items with individual routes?
I've put together a small sample. This structure works for me for all of my projects and quite fast to implement once you've got the hang of it.
Demo link
Route Structure
This pathIds variable is an object which holds all id values for routes. This will be useful when needing a reference to auto generate breadcrumb links.
const pathIds = {
home: 'home',
...
}
This pathRouting variable is an object which holds all path routing. Just path routing.
const pathRouting = {
home: '/home',
...
}
Primary structure object:
const pageRoutes = {
[pathIds.home]: {
path: pathRouting.home,
sidebarName: 'Homepage',
icon: Dashboard,
noRender: false,
component: Home,
},
...
}
This is primary structure object, holding all necessary info to generate onto HTML (sidebar, breadcrumbs, routes etc).
path: get the path value from pathRouting object. (Required)
sideName: text showing on Sidebar. (Required)
icon: usually this icon only shows on the sidebar. You can omit this if your menu/sidebar/navigation doesn't need it. (Optional)
noRender: boolean to indicate whether this object will be rendered to the sidebar. This is useful for routes that doesn't need to directly access (like a 404 Page Not Found page) (Optional)
component: The React component which will be used to render. (Required)
Then, in your app.js, you import pageRoutes as usual. Before doing mapping or iteration work, you might need to convert this object to an array (or use a library like lodash to directly iterating on the object).
const routeArray = Object.values(pageRoutes);
Then:
<Switch>
{routeArray.map((prop, key) => {
return (
<Route
path={prop.path}
component={prop.component}
exact={prop.exact || false}
key={`route-${key}`}
/>
);
})}
<Route component={pageRoutes[pathIds.error404].component} />
</Switch>
Notice the very last line where I explicitly declare a fallback <Route /> path in case user is on a wrong/ not defined path, that's how useful an object is in this kind of situation. It doesn't need path prop.
You sidebar is just a simple component receives a list of routes as props (as converted in app.js), then it can be used to show on the view.
<List component="nav" aria-label="main mailbox folders">
{routes.map(({ path, noRender, sidebarName, ...prop }, index) => {
if (noRender) return null;
return (
<NavLink to={path} key={`route-${index}}`}>
<ListItem button>
<ListItemIcon>
<prop.icon />
</ListItemIcon>
<ListItemText primary={sidebarName} />
</ListItem>
</NavLink>
);
})}
</List>
You will see it only renders Homepage, Inbox and Reset Password on sidebar. But you can directly enter /register to make <Register /> show up. The same for /page-not-found. Or even when you enter a wrong address like /register-user, the <PageNotFound /> component will be used for fallback case.
You might wonder why I don't put all path routes and path Ids in its own place, why do I need to move them into a separate object? The answer is, in my case I usually need quick access to those values. This might be useful for generating breadcrumbs or do some iteration without pulling the whole object. Depend on your specific project needs, those two might be shortened.
For full code, you can visit here: Sample Link
I have found a way to achieve this.
First I created an array of objects like:
const topics= [{
topic: "Home",
path: "home"
},
{
topic: "DataStructures",
path: "datastructures"
}]
then I did this:
<List>
{topics.map((text, index) => (
<ListItem button key={text.topic} component="a" href={text.path === "home"? "\\":text.path}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text.topic} />
</ListItem>
))}
</List>