Protected path redirect react-router problem [duplicate] - react-router

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}

Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}

For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f

import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.

Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>

If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

Related

how to create a Protected Route if I wrote route in the way: [duplicate]

How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)

React - I get error 404 from the server although the server working

I created cards and I added to evrey card the option mark like but I want that after the client mark like in some card, this card will save in another page of favorites. So I wrote the next codes and from some reson I get error 404 from the server (GET /api/cards/my-favorite-cards 404 4.389 ms)
MyFavoriteCard.jsx:
import React from "react";
import PageHeader from "../common/pageHeader";
import CardExtends from "../common/Cards/CardExtends";
import Cards from "../common/Cards/cards";
import { Navigate } from "react-router-dom";
import { getMyFavorite } from "../../services/cardService";
class Fav extends CardExtends {
state = {
data: [],
cards: [],
isMount: false,
};
async componentDidMount() {
try {
const { data } = await getMyFavorite();
this.setState({ data, cards: data, isMount: true });
} catch (error) {
console.log(error.message);
}
}
render() {
const { user } = this.props;
if (!user || (user && !user.biz)) return <Navigate replace to="/" />;
const cards = [...this.state.cards];
// const { isMount } = this.state;
// if (!isMount) return null;
return (
<React.Fragment>
<PageHeader
title="My Favorite Cards"
subTitle="Here you can find your favorite cards"
/>
<div className="container">
<Cards
cards={cards}
handleDelete={this.handleDelete}
changeLikeStatus={this.changeLikeStatus}
/>
</div>
</React.Fragment>
);
}
}
export default Fav;
cardService.js:
import http from "./httpService";
const apiUrl = process.env.REACT_APP_API_URL;
export const getCard = (cardId) => http.get(`${apiUrl}/cards/card/${cardId}`);
export const getCards = () => http.get(`${apiUrl}/cards/cards`);
export const getMyCards = () => http.get(`${apiUrl}/cards/my-cards`);
export const createCard = (card) => http.post(`${apiUrl}/cards/`, card);
export const deleteCard = (cardId) => http.delete(`${apiUrl}/cards/${cardId}`);
export const editCard = (card) => http.put(`${apiUrl}/cards/${card._id}`, card);
export const changeLikeStatus = async (cardId) =>
http.patch(`${apiUrl}/cards/card-like/${cardId}`);
export const getMyFavorite = () =>
http.get(`${apiUrl}/cards/my-favorite-cards`);
App.js:
import HomePage from "./layout/main/Home";
import About from "./layout/main/About.jsx";
import { Routes, Route } from "react-router-dom";
import Error404 from "./layout/main/Error404";
import Header from "./layout/header/Header.jsx";
import Footer from "./layout/footer/footer.jsx";
import BizSignup from "./layout/main/BizSignup";
import Logout from "./layout/main/Logout";
import MyCards from "./layout/main/MyCards";
import MyFavoriteCards from "./layout/main/MyFavoriteCards";
import Login from "./layout/main/Login";
import Signup from "./layout/main/Signup";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { getCurrentUser } from "./services/userService";
import CreateCard from "./layout/main/CreateCard";
import EditCardConvertor from "./layout/main/editCardConvertor";
import CardDetails from "./layout/main/CardDetails";
import CardDetailsConvertor from "./layout/main/CardDetailsConvertor";
function App() {
const user = getCurrentUser();
return (
<div className="App">
<Header user={user} />
<ToastContainer />
<main style={{ minHeight: "85vh" }}>
<Routes>
<Route path="/about" element={<About />} />
<Route path="/biz-signup" element={<BizSignup />} />
<Route path="/logout" element={<Logout />} />
<Route path="/my-cards" element={<MyCards user={user} />} />
<Route path="/create-card" element={<CreateCard user={user} />} />
<Route
path="/card-details-converotr"
element={<CardDetailsConvertor user={user} />}
/>
<Route
path="/edit-card/:id"
element={<EditCardConvertor user={user} />}
/>
<Route
path="/card-details/:id"
element={<CardDetails user={user} />}
/>
<Route
path="/my-fav-cards"
element={<MyFavoriteCards user={user} />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="/" element={<HomePage />} />
<Route path="*" element={<Error404 />} />
</Routes>
</main>
<Footer />
</div>
);
}
export default App;
cardsRouter.js
router.get("my-fav-cards", auth, async (req, res) => {
try {
const { _id } = req.user;
const favoriteCards = await Card.find({ likes: _id });
return res.send(favoriteCards);
} catch (error) {
console.log(chalk.redBright("Error: You can't save", error.message));
return res.status(500).send(error.message);
}
});
I would happy to know what the problem
Thnak you
Important to know: the page is exsit, there is title and subtitle in the page, but the card arent appear in the page.

how to redirect to partical page using history object in react-router-dom v6

I am using "react-router-dom" v6. When i try to redirect to another page using history object i got the following ERROR:
Cannot read properties of undefined (reading 'push')
Here is my code:
const Search = ({ history }) => {
const [keyword, setKeyword] = useState("");
const searchSubmitHandler = (e) => {
e.preventDefault();
if (keyword.trim()) {
history.push(`/products/${keyword}`);
} else {
history.push("/products");
}
};
}
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/product/:id" element={<ProductDetails />} />
<Route exact path="/products" element={<Products />} />
<Route exact path="/search" element={<Search />} />
</Routes>
<Footer />
</div>
</Router>
);
}
The history object was replaced by a navigate function in react-router-dom version 6, accessed via the useNavigate hook.
import { useNavigate } from 'react-router-dom';
const Search = () => {
const navigate = useNavigate();
const [keyword, setKeyword] = useState("");
const searchSubmitHandler = (e) => {
e.preventDefault();
navigate(keyword.trim() ? `/products/${keyword}` : "/products");
};
};

How to avoid flash before entering next route with loading in react router?

I would like to use prepare to prepare next page before entering route and at this point a loading page is shown.
I have tried to use useLocation and useEffect but next page will flash in before loading page is shown.
const [loading, setLoading] = useState(true);
const location = useLocation(); // react-router
useEffect(() => {
setLoading(true);
setTimeout(() => {
prepare(() => setLoading(false)); // callback when `prepare` is complete
}, 0);
}, [location.pathname, prepare, setLoading]);
return (
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
render={() => loading ? '' : <route.component />}
/>
))}
</Switch>
);
Currently I am using a dirty solution DelayedComponent
const DelayedComponent = (props) => {
const { component } = props;
const [show, setShow] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setShow(true);
}, 1000);
return () => {
clearTimeout(timeout);
}
}, []);
return show ? createElement(component) : <div />;
};
I am not sure, that i understood your problem correctly, but i will try to suggest something
It's your router:
<Switch>
{routes.map(({ path, component: Component }) => (
<Route
exact
key={path}
path={path}
>
<Component />
</Route>
))}
</Switch>
then your any component
// Users.js
import React from 'react';
import useFetchUsers from './useFetchUsers';
const Users = () => {
const { isLoadingUsers, users } = useFetchUsers();
if (isLoadingUsers) {
return <LoadingWhatEverComponent />
}
return (
<div>{users.map((user) => user.name)}</div>
)
}
Before users are not loaded you will show loading screen
// useFetchUsers.js
import { useCallback, useState, useEffect } from 'react';
export default () => {
const [isLoading, setIsLoading] = useState(true);
const [users, setUsers] = useState([]);
const fetchUsers = useCallback(async () => {
try {
const response = await axios(...);
setUsers(response.data.users);
} catch (error) {
console.error(error);
setUsers([]);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
fetchUsers();
}, [])
return {
isLoadingUsers: isLoading,
users
}
}
as a result, before fetchUsers is not over, you can see only loading component

React Router Nest Routes Without JSX

I'm using webpack 2 / code splitting so I have my routes like so
import App from './components/App';
import Portal from './components/portal/Portal';
const componentRoutes = {
component: App,
path: '/',
indexRoute: { component: Portal },
childRoutes: [
{
path: 'login',
getComponent(location, cb) {
System.import('./components/login/Login')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id',
getComponent(location, cb) {
System.import('./components/homepage/HomeContainer')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id/assessments',
getComponent(location, cb) {
System.import('./components/assessment/AssessmentContainer')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id/manage_classes',
getComponent(location, cb) {
System.import('./components/manageClasses/manageClassesContainer')
.then(module => cb(null, module.default));
}
}
]
};
But I want to nest my routes like this:
import Login from './components/login/Login';
import HomeContainer from './components/homepage/HomeContainer';
import Unit from './components/homepage/Units'
import AssessmentContainer from './components/assessment/AssessmentContainer';
import ManageClassesContainer from './components/manageClasses/manageClassesContainer';
const componentRoutes = (
<Route path='/' component={App}>
<Route path='login' component={Login} />
<Route path='home/:id' component={HomeContainer}>
<IndexRoute component={Unit} />
<Route path='home/:id/assessments' component={AssessmentContainer} />
<Route path='home/:id/manage_classes' component={ManageClassesContainer} />
</Route>
</Route>
);
How do you have nested routes inside the nested routes array? When I navigate to home/:id I don't want to lose all the JSX HomeContainer provides and I just want to call use {props.children} to render the child routes. When you use code splitting you can't use JSX so that's why I'm trying to refactor.