I need a nested route in react router V5 - javascript

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"))
}
];

Related

Route inside route not work for me in react

I have a website that I'm building and an admin dashboard that I want to go to. I have a page that has an <Outlet/> inside it, and only when I go to the admin-dashboard address it works, but if I add a path, it leaves me from the page where I have the <Outlet/>.
This is my page with <Outlet/> code:
const Layout = (props) => {
const [open, setOpen] = useState(false);
const dreWidth = window.screen.width > 1000 ? open ? 300 : 100 : null
return (
<Box>
{props.adminPath && <MiniDrawer open={open} setOpen={setOpen} logged={props.logCheck} auth={props.auth}/>}
<Box
component='main'
style={{ flexGrow: 1, padding: 3, width: `calc(100% - ${dreWidth}px)` , transition : '0.5s' , float: 'left' }}
>
<Toolbar style={{ minHeight: "1.875rem" }} />
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
</Box>
</Box>
);
};
export default Layout;
This is main with the Routers :
const MainPage = () => {
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const { pathname } = useLocation()
useEffect(() => {
dispatch(LoadUser())
dispatch(getAllUsers())
}, [dispatch])
let logCheck = token
let user = users.find(user => user.id === auth.id)
let adminPath = pathname === '/admin-dashboard'
return (
<>
<Box>
{!adminPath && <Header logged={logCheck} user={user} auth={auth}/>}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<Contact />} />
<Route path="/join-to-course" element={logCheck ? <Navigate to='/'/> :<JoinToCourse />} />
<Route path="/join" element={logCheck ? <Navigate to='/'/> : <MainJoinToCourse />} />
<Route path="/join/:orderId" element={logCheck ? <Navigate to='/'/> : <PaymentPage />} />
<Route path="/login" element={logCheck ? <Navigate to='/'/> : <LoginPage />} />
<Route path="admin-dashboard" element={logCheck ? <Layout adminPath={adminPath} user={user} auth={auth}/> : <Navigate to='/' />}>
<Route path="" element={<Dashboard />}/>
<Route path="home-page" element={<HomePageContents />}/>
</Route>
</Routes>
</Box>
<Box>
<BackToTop />
</Box>
{!adminPath && <Footer />}
</>
);
};
export default MainPage;
Doesn't matter I just changed the code
from this :
let adminPath = pathname === '/admin-dashboard'
to this :
let adminPath = pathname.includes('/admin-dashboard')
now its work
As I understand your question/issue you appear to want to conditionally render the Header component on non-admin routes.
Create another layout that renders Header and an Outlet, similar to the existing Layout component.
Non-Admin layout
const Layout = () => {
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const user = users.find(user => user.id === auth.id);
return (
<>
<Header logged={token} user={user} auth={auth}/>
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
<Box>
<BackToTop />
</Box>
<Footer />
</>
);
};
Admin layout
const AdminLayout = () => {
const auth = useSelector((state) => state.auth.auth);
const token = useSelector((state) => state.auth.token);
const users = useSelector((state) => state.users.users);
const [open, setOpen] = useState(false);
const dreWidth = window.screen.width > 1000 ? open ? 300 : 100 : null
return (
<Box>
<MiniDrawer open={open} setOpen={setOpen} logged={token} auth={auth} />
<Box
component='main'
style={{
flexGrow: 1,
padding: 3,
width: `calc(100% - ${dreWidth}px)`,
transition : '0.5s',
float: 'left'
}}
>
<Toolbar style={{ minHeight: "1.875rem" }} />
<Box style={{ margin: "1rem 2rem" }}>
<Outlet />
</Box>
</Box>
</Box>
);
};
It seems you are also using a token value stored in state to conditionally render a routed component or the Navigate component. For simplicity of routing I'd suggest also creating protected layout routes as well.
ProtectedRoute
import { Outlet } from 'react-router-dom';
const ProtectedRoute = () => {
const token = useSelector((state) => state.auth.token);
if (token === undefined) {
return null;
}
return token ? <Outlet> : <Navigate to='/' replace />;
};
AnonymousRoute
import { Outlet } from 'react-router-dom';
const AnonymousRoute = () => {
const token = useSelector((state) => state.auth.token);
if (token === undefined) {
return null;
}
return token ? <Navigate to='/' replace /> : <Outlet>;
};
Main
const MainPage = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(LoadUser());
dispatch(getAllUsers());
}, [dispatch]);
return (
<Box>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<Contact />} />
<Route element={<AnonymousRoute />}>
<Route path="/join-to-course" element={<JoinToCourse />} />
<Route path="/join" element={<MainJoinToCourse />} />
<Route path="/join/:orderId" element={<PaymentPage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
</Route>
<Route element={<ProtectedRoute />}>
<Route path="admin-dashboard" element={<AdminLayout />}>
<Route index element={<Dashboard />} />
<Route path="home-page" element={<HomePageContents />} />
</Route>
</Route>
</Routes>
</Box>
);
};

How to pass a function to state in react router v6

I want to share state between two routes when I click on the link for one of the routes (NewUser). The state that I want to share and the logic modifying it are both held in the Users route. I want to pass the logic to change the state to the NewUsers route.
When I pass a string to the state object in router Link, I am able to access it in the NewUsers component. However, I get null when I pass a function.
I know that I can use context/redux, but I would prefer if I can do it this way.
Users route:
function Users() {
const [users, setUsers] = useState([]);
return (
<Card sx={{ padding: "2rem", mt: "2rem" }}>
<MDBox
display="flex"
flexDirection="row"
justifyContent="space-between"
>
<MDTypography variant="body2">{`You currently have ${users.length} users`}</MDTypography>
<MDButton variant="gradient" color="info" size="small">
<Link to="/settings/users/new-user" state={setUsers: setUsers}> //this is how I want to pass the state
<MDBox
display="flex"
alignItems="center"
color="white"
fontWeight="normal"
>
<Icon>add</Icon> Add New User
</MDBox>
</Link>
</MDButton>
</MDBox>
</Card>
NewUsers route:
function NewUser({history}) {
const location = useLocation();
const saveChanges = (e) => {
location.state.setUsers({
fname: values.firstName,
lname: values.lname,
email: values.email,
});
navigate("/settings/users");
};
return(
<MDBox py={3} mb={20} height="62vh">
<Grid
container
justifyContent="center"
alignItems="center"
sx={{ height: "100%", mt: 0 }}
>
<Grid item xs={12} lg={12}>
<Formik
initialValues={initialValues}
validationSchema={currentValidation}
onSubmit={(values) => {
setValues(values);
}}
>
{({ values, errors, touched, isSubmitting }) => (
<Form id={formId} autoComplete="off">
<Card sx={{ height: "100%", width: "100%" }}>
<MDBox px={3} py={4}>
<MDBox display="flex">
<ButtonWrapper
fullWidth={false}
handleClick={saveChanges}
>
Save Changes
</ButtonWrapper>
</MDBox>
<MDBox>
{getStepsContent({
values,
touched,
formField,
errors,
})}
</MDBox>
</MDBox>
</Card>
</Form>
)}
</Formik>
</Grid>
</Grid>
</MDBox>
)
}
Routing code:
{
type: "collapse",
name: "Settings",
key: "settings",
icon: <Icon fontSize="small">settings</Icon>,
collapse: [
{
name: "Users",
key: "users",
route: "/settings/users",
// icon: <Icon fontSize="small">users</Icon>,
component: <Users />,
},
{
name: "Companies",
key: "companies",
route: "/settings/companies",
component: <Companies />,
},
{
name: "Billing",
key: "billing",
route: "/settings/billing",
component: <Billing />,
},
{
name: "Integrations",
key: "integrations",
route: "/settings/integrations",
component: <Integrations />,
},
],
},
{
name: "New User",
key: "new user",
route: "/settings/users/new-user",
noCollapse: true,
component: <NewUser />,
},
{
type: "collapse",
name: "Sign Out",
key: "signout",
route: "/sign-out",
icon: <Icon fontSize="small">logout</Icon>,
component: <SignOut />,
noCollapse: true,
},
];
function that renders the routes:
const getRoutes = (allRoutes) =>
allRoutes.map((route) => {
if (route.collapse) {
return getRoutes(route.collapse);
}
if (route.route) {
return <Route exact path={route.route} element={route.component} key={route.key} />;
}
return null;
});
<Routes>
{getRoutes(routes)}
{/* <Route path="*" element={<Navigate to="/dashboard" />} /> */}
<Route path="*" element={<Console />} />
</Routes>
The state value sent via the Link component needs to be JSON serializable. Javascript functions are not serializable. Instead of trying to pass a function through to a target component I recommend lifting the state up to a common ancestor so the state and callback function is accessible to both components.
I would suggest using a React context to hold the users state and provide out the state value and an updater function to add a user object. react-router-dom has a "built-in" way to do this via a layout route component that renders an Outlet component that wraps nested routes.
Example:
import { Outlet } from 'react-router-dom';
const UsersProvider = () => {
const [users, setUsers] = useState([]);
const addUser = (user) => {
setUsers((users) => users.concat(user));
};
return <Outlet context={{ users, addUser }} />;
};
...
<Routes>
...
<Route path="/settings/users" element={<UsersProvider />}>
<Route index element={<Users />} />
<Route path="new-user" element={<NewUser />} />
</Route>
...
</Routes>
Users
const Users = () => {
const { users } = useOutletContext();
return (
<Card sx={{ padding: "2rem", mt: "2rem" }}>
<Box display="flex" flexDirection="row" justifyContent="space-between">
<Typography variant="body2">
You currently have {users.length} users
</Typography>
<Button variant="gradient" color="info" size="small">
<Link to="/settings/users/new-user">
<Box
display="flex"
alignItems="center"
color="white"
fontWeight="normal"
>
<Icon>add</Icon>
Add New User
</Box>
</Link>
</Button>
</Box>
</Card>
);
};
NewUser
function NewUser({history}) {
const navigate = useNavigate();
const { addUser } = useOutletContext();
const saveChanges = (e) => {
addUser({
fname: values.firstName,
lname: values.lname,
email: values.email,
});
navigate("/settings/users");
};
return(
<MDBox py={3} mb={20} height="62vh">
<Grid
container
justifyContent="center"
alignItems="center"
sx={{ height: "100%", mt: 0 }}
>
<Grid item xs={12} lg={12}>
<Formik
initialValues={initialValues}
validationSchema={currentValidation}
onSubmit={(values) => {
setValues(values);
}}
>
{({ values, errors, touched, isSubmitting }) => (
<Form id={formId} autoComplete="off">
<Card sx={{ height: "100%", width: "100%" }}>
<MDBox px={3} py={4}>
<MDBox display="flex">
<ButtonWrapper
fullWidth={false}
handleClick={saveChanges}
>
Save Changes
</ButtonWrapper>
</MDBox>
<MDBox>
{getStepsContent({
values,
touched,
formField,
errors,
})}
</MDBox>
</MDBox>
</Card>
</Form>
)}
</Formik>
</Grid>
</Grid>
</MDBox>
)
}

MUI Persistent Drawer - Routing Issues

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.

Clicking a button shows the the url but not taking me to the page I wanted

I'm using useHistory() for routing different page
I'm using materialui drawer and when clicking to the listitem button I want to go to another page
but its only showing me the url but not taking me to the page
<List>
{sec.map((text, index) => (
<ListItem
button
key={text.title}
onClick={() => history.push(text.path)}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText
primary={text.title} />
</ListItem>
))}
</List>
In app.js I create route :
<BrowserRouter >
< SideDrawer />
<Switch>
<Route path="/" component={Home} exact />
<Route path="/design" component={Design} />
<Route path="/technology" component={Technology} />
</Switch>
</BrowserRouter>
in hooks use this approach
import { useHistory} from "react-router-dom";
const yourComponent = () => {
const history = useHistory();
const yourPath = '/your/url/path';
const openUrl = () =>{ // handler to open url
history.push({
pathname: yourPath
})
}
}
This shall fix your issue
import { useHistory } from "react-router-dom";
const HomeButton = () => {
const history = useHistory();
const handleClick = () => {
// add your path here
history.push("/home");
};
return (
<List>
{sec.map((text, index) => (
<ListItem button key={text.title} onClick={handleClick}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text.title} />
</ListItem>
))}
</List>
);
};
export default HomeButton;

How can I wrap my light/dark mode theme around index.js while using my header as the switch location?

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.

Categories

Resources