I am implementing dynamic routing in my project to open different pages according to user roles according to this git repo:
https://github.com/javaLuo/react-admin
It works fine on initial loading pages.
However, it returns blank page whenever I try to refresh any page in BasicLayout instead of the login page.
index.js:
import React, { useEffect, useState } from "react";
import { Navigate, BrowserRouter, Routes, Route, Outlet, Router } from 'react-router-dom';
import { createHashHistory as createHistory} from "history";
import { connect} from "react-redux";
import tools from "../util/tools";
import BasicLayout from "../layouts/BasicLayout";
import UserLayout from "../layouts/UserLayout";
const history = createHistory();
const RouterContainer = (props) => {
useEffect(() => {
const userinfo = localStorage.getItem("userinfo");
if(userinfo){
props.setUserInfo(JSON.parse(userinfo));
}
},[]);
const onEnter = (Component, props) => {
const userinfo = localStorage.getItem("userinfo");
if (userinfo) {
return <Component {...props} />;
}
return <Navigate to="/public/login"/>;
};
return (
<BrowserRouter history={history}>
<Routes>
<Route path="/public/*" element={<UserLayout />} />
<Route path="/*" element={onEnter(BasicLayout, props)}/>
</Routes>
</BrowserRouter>
);
};
export default connect(
(state) => ({
userinfo: state.app.userinfo,
}),
(dispatch) => ({
setUserInfo: dispatch.app.setUserInfo,
})
)(RouterContainer);
The onEnter function will redirect to login page if there is no login record in the localeStorage.
BasicLayout.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { Route, Routes, Navigate, useLocation, useNavigate } from "react-router-dom";
import Loadable from "react-loadable";
import tools from "../util/tools";
import { Layout, message } from "antd";
//... reduced to prevent too much code
// Router
const [NotFound, NoPower, Home,AlertNdr, MenuAdmin, PowerAdmin, RoleAdmin, UserAdmin] = [
() => import(`../views/error/404`),
() => import(`../views/error/401`),
() => import(`../views/Home`),
() => import(`../views/analytic/AlertNdr`),
() => import(`../views/sys/MenuAdmin`),
() => import(`../views/sys/PowerAdmin`),
() => import(`../views/sys/RoleAdmin`),
() => import(`../views/sys/UserAdmin`),
].map((item) => {
return Loadable({
loader: item,
loading: Loading,
});
});
const { Content } = Layout;
const BasicLayout = (props) => {
const [collapsed, setCollapsed] = useState(false);
const location = useLocation();
const onToggle = () => setCollapsed(!collapsed);
let history = useNavigate();
const onLogout = () => {
props.onLogout().then(() => {
message.success("退出成功");
history.push("/");
});
};
const checkRouterPower = (pathname) => {
let menus;
console.log(pathname)
if (props.userinfo.menu.rows && props.userinfo.menu.rows.length) {
menus = props.userinfo.menu.rows;
} else if (localStorage.getItem("userinfo")) {
menus = JSON.parse(localStorage.getItem("userinfo")).menu.rows;
}
const m = menus.map((item) => item.url.replace(/^\//, ""));
const urls = pathname.split("/").filter((item) => !!item);
for (let i = 0; i < urls.length; i++) {
if (!m.includes(urls[i])) {
return false;
}
}
return true;
};
const onEnter = (Component, props) =>{
if(checkRouterPower(location.pathname)){
console.log("test");
return <Component {...props} />
}
return <Navigate to="/nopower" />
};
return (
<Layout className="page-basic">
<Menu
data={props.userinfo.menu.rows}
collapsed={collapsed}
location={props.location}
history={history}
/>
<Layout>
<Header
collapsed={collapsed}
userinfo={props.userinfo}
onToggle={onToggle}
onLogout={onLogout}
/>
<Content className="content">
<Routes>
<Route path="/" element={<Navigate to="/home" />}/>
<Route path="/home" element={onEnter(Home, props)}/>
<Route path="/analytic/alertNdr" element={onEnter(AlertNdr, props)}/>
<Route path="/system/menuadmin" element={onEnter(MenuAdmin, props)}/>
<Route path="/system/poweradmin" element={onEnter(PowerAdmin, props)}/>
<Route path="/system/roleadmin" element={onEnter(RoleAdmin, props)} />
<Route path="/nopower" element={<NoPower/>} />
<Route path="*" element={<NotFound/>} />
</Routes>
</Content>
</Layout>
</Layout>
);
};
export default connect(
(state) => ({
userinfo: state.app.userinfo,
}),
(dispatch) => ({
onLogout: dispatch.app.onLogout,
})
)(BasicLayout);
Try changing the useEffect to useMemo in your index.js:
useMemo(() => {
const setUserInfo = async (parsedUserinfo) => {
await props.setUserInfo(parsedUserinfo);
}
const userinfo = localStorage.getItem("userinfo");
if(userinfo){
const parsedUserinfo = JSON.parse(userinfo);
setUserInfo(parsedUserinfo)
}
},[]);
element is optional, and it is either React.ReactNode or null
Try this.
const OnEnter = ({ children }) => {
const userinfo = localStorage.getItem("userinfo");
if (userinfo) {
return <>{children}</>
}
return <Navigate to="/public/login"/>;
};
return (
<BrowserRouter history={history}>
<Routes>
<Route path="/public/*" element={<UserLayout />} />
<Route path="/*" element={<Onter><BasicLayout {...props}</OnEnter>}/>
</Routes>
</BrowserRouter>
);
You must also change all the onEnters in BasicLayout
Related
I am trying to destructure the onLogout function from useAuth which returns useContext(AuthContext), and while doing this, I get an error in the console:
Error:
Uncaught TypeError: Cannot destructure property 'onLogout' of 'useAuth(...)' as it is null.
App.js code snippet:
import React, { createContext, useContext } from "react";
import { Navigate, Route, Routes, useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { logout, userLogin, userRegister } from "./features/cv";
import { cookieCutter } from "./utils/cookie";
const AuthContext = createContext(null);
const AuthProvider = ({ children }) => {
const token = cookieCutter("token");
const dispatch = useDispatch();
const navigate = useNavigate();
const handleLogin = (data) => {
dispatch(userLogin(data));
navigate("/table");
};
const handleRegister = (data) => {
dispatch(userRegister(data));
navigate("/table");
};
const handleLogout = () => {
dispatch(logout());
navigate("/login");
};
const value = {
token,
onLogin: handleLogin,
onRegister: handleRegister,
onLogout: handleLogout,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
const useAuth = () => {
return useContext(AuthContext);
};
function App() {
const ProtectedRoute = ({ children }) => {
const { token } = useAuth();
if (!token) {
return <Navigate to="/login" replace />;
}
return children;
};
const userString = JSON.parse(cookieCutter("user") ?? "{}");
const { onLogout } = useAuth();
return (
<AuthProvider>
<div>
<Routes>
<Route
path="/"
element={
<ProtectedRoute>
<Create />
</ProtectedRoute>
}
/>
<Route
path="/table"
element={
<ProtectedRoute>
<Table />
</ProtectedRoute>
}
/>
<Route
path="/preview"
element={
<ProtectedRoute>
<Preview />
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
</Routes>
</div>
</AuthProvider>
)
But when I do the same inside the ProtectedRoute function to get the token it works without any error, don't know why.
App.js ProtectedRoute function:
const ProtectedRoute = ({ children }) => {
const { token } = useAuth();
if (!token) {
return <Navigate to="/login" replace />;
}
return children;
};
You cannot use useAuth() in App, because it's not a child of AuthProvider. For this to work, move AuthProvider call to the index file to have something like this:
<AuthProvider>
<App/>
</AuthProvider>
Also, it's not correct to define a component inside another one. Move ProtectedRoute outside of App:
const ProtectedRoute = ({ children }) => {
const { token } = useAuth();
if (!token) {
return <Navigate to="/login" replace />;
}
return children;
};
I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
Recently, I am facing issue with Custom Route with latest react-router-dom version 6 in my code.
I have created custom route for authorized if user is not login then it will redirect to sign in page. It was working with previous react router version but now it's got broke with latest one.
I am posting the code below.
Getting below error message as well.
[UserRoute] is not a component. All component children of must be a Route or <React.Fragment>
LoadingToRedirect.js
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
const navigate = useNavigate();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
count === 0 && navigate("/login");
return () => clearInterval(interval);
}, [count, navigate]);
return (
<div>
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
UserRoute.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import { useSelector } from "react-redux";
import LoadingToRedirect from "./LoadingToRedirect";
const UserRoute = ({ children, ...rest }) => {
const { currentUser } = useSelector((state) => state.user);
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
};
export default UserRoute;
App.js
import React, { useEffect, Fragment } from "react";
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import UserRoute from "./components/UserRoute";
import { useDispatch } from "react-redux";
import { auth } from "./firebase";
import { setUser } from "./redux/actions";
import Header from "./components/Header";
import AddEdit from "./pages/AddEdit";
function App() {
const dispatch = useDispatch();
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
dispatch(setUser(authUser));
} else {
dispatch(setUser(null));
}
});
}, [dispatch]);
return (
<BrowserRouter>
<div className="App">
<Header />
<ToastContainer position="top-center" />
<Fragment>
<Routes>
<UserRoute path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<UserRoute path="/addContact" element={<AddEdit />} />
</Routes>
</Fragment>
</div>
</BrowserRouter>
);
}
export default App;
In react-router v6, the Routes component allows only Route children.
In UserRoute.js if there's no user you are returning LoadingToRedirect:
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
You can try changing this to:
const UserRoute = ({ children, path, element }) => {
const { currentUser } = useSelector((state) => state.user);
return <Route path={path} element={ currentUser ? element : <LoadingToRedirect />} />;
};
I want to implement a case where when users are logged in that they need to able to access the AppStack and the AuthStack. And if they not logged in, only the AuthStack will be accessible. Also when they try to access the AppStack they should get redirected. In react native I could handle this with a AuthStack and if something changed the user status he could see the one stack or the other one. But I don't know how to do it with reactjs
AuthNavigator
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return user ? <AppStack user={user} /> : <AuthStack user={user} />;
}
export default AuthNavigator;
AppStack here i tried it like if there is a user then you can render the appStack else you get redirected to the authstack.
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Overview from '../pages/app/Overview';
import Sidebar from '../components/Sidebar/Sidebar';
function AppStack({ user }) {
return this.props.user ? (
<Router>
<Switch>
<Sidebar user={user} />
<Route path="/app-melior/overview" exact component={Overview}></Route>
</Switch>
</Router>
) : (
<Redirect path="/auth/signin"></Redirect>
);
}
export default AppStack;
AuthStack
import React from 'react';
import Navbar from '../components/Navbar/Navbar';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import SignIn from '../pages/auth/SignIn';
function AuthStack({ user, app }) {
return (
<>
<Router>
<Switch>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>
</>
);
}
export default AuthStack;
I usually create a Private route and hanlde it like this
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
const PrivateRoute = ({ component, ...rest }) => {
return (
<Route
{...rest}
render={(props) =>
user ? (
<Component {...props}></Component>
) : (
<Redirect to="/auth"></Redirect>
)
}
></Route>
);
};
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return
<Router>
<Switch>
<PrivateRoute path="/app>
<Sidebar user={user} />
<PrivateRoute path="/app-melior/overview" exact component={Overview}></PrivateRoute>
</PrivateRoute>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>;
}
export default AuthNavigator;
Please help me I am completely messed with my code and I am getting this error and I am unable to resolve it.
I know there are several answers to this question but they didn’t resolve my problem. I am new to react and am unable to find the correct code.
19 | useEffect(() => {
20 | const user =
JSON.parse(localStorage.getItem("user"));
21 | if (user) {
> 22 | dispatch({type:"USER",payload:user})
^ 23 | } else {
24 | history.push("/signin");
25 | }
App.js
import React, { useEffect, createContext, useReducer, useContext } from "react";
import Navbar from "./components/Navbar";
import "./App.css";
import { BrowserRouter, Route, Switch, useHistory } from "react-router-dom";
import Home from "./components/screens/Home";
import Signin from "./components/screens/Signin";
import Signup from "./components/screens/Signup";
import Profile from "./components/screens/Profile";
import CreatePost from "./components/screens/CreatePost";
import UserProfile from "./components/screens/UserProfile";
// import Hero from "./components/screens/Hero";
import { reducer, initialState } from "./reducers/userReducer";
export const UserContext = createContext();
const Routing = () => {
const history = useHistory();
const { state, dispatch } = useContext(UserContext);
useEffect(() => {
const user = JSON.parse(localStorage.getItem("user"));
if (user) {
dispatch({type:"USER",payload:user})
} else {
history.push("/signin");
}
}, []);
return (
<Switch>
<Route exact path="/">
<Home />
</Route>
{/* <Route exact path="/post">
<Hero />
</Route> */}
<Route path="/signin">
<Signin />
</Route>
<Route path="/signup">
<Signup />
</Route>
<Route exact path="/profile">
<Profile />
</Route>
<Route path="/create">
<CreatePost />
</Route>
<Route path="/profile/:userid">
<UserProfile />
</Route>
</Switch>
);
};
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<UserContext.Provider value={(state, dispatch)}>
<BrowserRouter>
<Navbar />
<Routing />
</BrowserRouter>
</UserContext.Provider>
);
}
export default App;
userReducer.js
export const initialState = null;
export const reducer = (state, action) => {
if (action.type === "USER") {
return action.payload;
}
if (action.type === "CLEAR") {
return null;
}
if (action.type === "UPDATE") {
return {
...state,
followers: action.payload.followers,
following: action.payload.following,
};
}
return state;
};