I'm a beginner and I'm currently developing an application using a React Template. The template uses React Router v6 for the router with use Routes() hook.
Every route of the application is protected and can be only accessed by logging in
I'm planning to implement login using use Context() hook but I cant seem to figure out how to wrap the routes in the Provider tag.
My two doubts are:
How do I wrap my routes in the <Context Provider> tag
Should I wrap all my routes in an application like this.
First of all you will need the Context. I always prefer to write a hook:
import { createContext, useContext, useState } from "react";
const AuthContext = createContext({
isAuthenticated: false,
login: () => {},
logout: () => {}
});
export function AuthProvider({ children }){
const [isAuthenticated, setAuthenticated] = useState(false);
const login = () => {
setAuthenticated(true);
}
const logout = () => {
setAuthenticated(false);
}
return (
<AuthContext.Provider value={{isAuthenticated: isAuthenticated, login: login, logout: logout}}>
{children}
</AuthContext.Provider>
)
}
export default function AuthConsumer() {
return useContext(AuthContext);
}
Then you will need a private route component like this:
import React from 'react';
import { Navigate } from 'react-router-dom';
import useAuth from "./useAuth";
function RequireAuth({ children, redirectTo }) {
const { isAuthenticated } = useAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
export default RequireAuth;
Finally you will mix in your routes:
import React from 'react';
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
import Login from './pages/Login';
import RequireAuth from './components/PrivateRoute';
import useAuth from "./components/useAuth";
const Home = () => <div><h1>Welcome home</h1></div>
const Dashboard = () => <h1>Dashboard (Private)</h1>;
function App() {
const { isAuthenticated } = useAuth();
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={
<RequireAuth redirectTo="/login">
<Dashboard />
</RequireAuth>
}>
</Route>
<Route path="/login" element={<Login />} />
<Route path='*' element={<Navigate to={isAuthenticated ? '/dashboard' : '/'} />} />
</Routes>
</BrowserRouter>
);
}
export default App;
I hope this will help.
Related
I have a Navigation bar that I want to live on all of my pages except for my dashboard page.
The Goal
My login and signup are just pop ups from buttons in the navigation bar. Once my user is authenticated with firebase authentication they will be redirected to their dashboard. once redirected I don't want the navigation component to be displayed in the dashboard.
How do I go about doing this? I tried HOC (higher order components) but wasn't working, maybe i was doing something wrong?
I am also trying to use withRouter and make a function that checks if the path is /dashboard then navigation won't display.
APP.JS
import {
BrowserRouter as Router,
Route,
Routes,
withRouter,
} from "react-router-dom";
import { Navigation } from "./component/Navigation";
import { Homepage } from "./pages/homepage";
import { Pricing } from "./component/pricing";
import { Popup } from "./pages/popup";
import { Login } from "./pages/login";
import { AuthProvider } from "./context/authContext";
import { UserDashboard } from "./pages/dashboard";
function App(props) {
const [btnState, setBtnState] = useState(false);
const [loginState, setLoginState] = useState(false);
const handleDelete = () => {
{
this.props.location.pathname !== "/dashboard" && <Navigation />;
}
};
const handleClick = () => {
// toggle between true and false
console.log(setBtnState);
if (!btnState) {
setBtnState(true);
}
};
const handleLogin = () => {
console.log(setLoginState);
if (!loginState) {
setLoginState(true);
}
};
return (
<Router>
<div>
<Navigation
navDel={handleDelete}
handleit={handleClick}
handleTheLogin={handleLogin}
/>
<AuthProvider>
<Login trigger={loginState} setTrigger={setLoginState} />
<Popup trigger={btnState} setTrigger={setBtnState} />
<Routes>
<Route path="/" exact element={<Homepage />} />
<Route
path="/dashboard"
element={<UserDashboard deleteNav={handleDelete} />}
/>
<Route path="/pricing" element={<Pricing />} />
</Routes>
</AuthProvider>
</div>
</Router>
);
}
export default App;
I'm new to ReactJS and am building a basic application. Here I'm using protected router and implementing an authorization mechanism with useContext and local storage. The aim is to redirect users that are not logged in to the Login Page if they attempt to access Dashboard.
After I do log in, the access token is saved to local storage and account info is saved in auth context. Then I go to Dashboard and I reload the page. I implemented a useEffect hook to check for token in local storage and I thought that when I reload at the Dashboard page, the auth provider would check for the token and return a result that I'm authenticated. However it doesn't work as expected so I am redirected to Login page (Although the useEffect callback was triggered)
Below is my code:
src\components\App\index.js
import { Routes, Route } from 'react-router-dom';
import Login from '../Login';
import Signup from '../Signup';
import GlobalStyles from '../GlobalStyles';
import ThemeProvider from 'react-bootstrap/ThemeProvider';
import RequireAuth from '../RequireAuth';
import Layout from '../Layout';
import Dashboard from '../Dashboard';
import Account from '../Account';
function App() {
return (
<GlobalStyles>
<ThemeProvider>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route element={<RequireAuth />}>
<Route path="/" element={<Layout />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/account" element={<Account />} />
</Route>
</Route>
</Routes>
</ThemeProvider>
</GlobalStyles>
);
}
export default App;
src\components\RequireAuth\index.js
import { useLocation, Navigate, Outlet } from 'react-router-dom';
import useAuth from '../../hooks/useAuth';
function RequireAuth() {
const { auth } = useAuth();
const location = useLocation();
return auth?.user ? (
<Outlet />
) : (
<Navigate to={{ pathname: '/', state: { from: location } }} replace />
);
}
export default RequireAuth;
src\hooks\useAuth.js
import { useContext } from 'react';
import { AuthContext } from '../context/AuthProvider';
function useAuth() {
return useContext(AuthContext);
}
export default useAuth;
src\context\AuthProvider.js
import { useEffect } from 'react';
import { createContext, useState } from 'react';
import api from '../helper/api';
const AuthContext = createContext({});
function AuthProvider({ children }) {
const [auth, setAuth] = useState({});
useEffect(() => {
const apiHelper = new api();
apiHelper.getAccountInfo().then((response) => {
setAuth(response.data);
});
}, []);
console.log(auth.user);
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
);
}
export { AuthContext, AuthProvider };
src\components\Login\index.js
import { useState, useRef, useEffect } from 'react';
import clsx from 'clsx';
import { Form, FormGroup, Button } from 'react-bootstrap';
import Alert from 'react-bootstrap/Alert';
import FloatingLabel from 'react-bootstrap/FloatingLabel';
import { useNavigate } from 'react-router';
import styles from './style.module.scss';
import logo from '../../assets/images/logo.png';
import LoadingSpinner from '../LoadingSpinner';
import api from '../../helper/api';
import useAuth from '../../hooks/useAuth';
const Login = () => {
const usernameRef = useRef();
const errorRef = useRef();
const [state, setState] = useState({
username: '',
password: ''
});
const { username, password } = state;
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
usernameRef.current.focus();
}, []);
const { setAuth } = useAuth();
const navigate = useNavigate();
const submitForm = (event) => {
event.preventDefault();
setLoading(true);
const apiHelper = new api();
apiHelper
.login({
username,
password
})
.then((response) => {
setLoading(false);
setError();
setAuth({ user: response.data.user });
localStorage.setItem('token', response.data.token);
navigate('/dashboard');
})
.catch((error) => {
setLoading(false);
setError(error.response.data.message);
usernameRef.current.focus();
});
};
return (
/** Some hmtml */
);
};
export default Login;
A video on how the error occurs: https://streamable.com/b1cp1t
Can anyone tell me where I'm wrong and how to fix it? Many thanks in advance!
you can think about a <Route> kind of like an if statement, if its path matches the current URL, it renders its element!
since the path of your first route in the list is "/" to the login page matches the need of the router it will redirect you there.
so, if you will delete this line:
<Route path="/" element={<Login />} />
and let the <RequireAuth /> take care of the path="/" it will check first if the user is logged in, if so let him continue.
if not it will redirect to "/login"
I'm creating a protected route for my react project but context values and redux reducers data are not persistent. So what is the optimal way to set, for example isVerified to true if the user is logged. If isVerified === true go to homepage else redirect to login, isVerified needs to be mutated every change in route or refresh, because context or redux data is not persistent.
Do I need to create a separate backend api for just checking the token coming from the client side? then I will add a useEffect in the main App.tsx, something like:
useEffect(() => {
/*make api call, and pass the token stored in the localStorage. If
verified success then: */
setIsVerified(true)
}, [])
Then I can use the isVerified to my protected route
You can create a Middleware component wrapping both, Protected and Non-protected components routes. And inside of each just check if the user is logged, then render conditionally.
This is usually how I implemented,
Protected:
// AuthRoute.js
import React, { useEffect, useState } from "react";
import { Redirect, Route } from "react-router-dom";
export const AuthRoute = ({ exact = false, path, component }) => {
const [isVerified, setIsVerified] = useState(false);
const checkLoginSession = () => {
// Write your verifying logic here
const loggedUser = window.localStorage.getItem("accessToken") || "";
return loggedUser !== "" ? true : false;
};
useEffect(() => {
(async function () {
const sessionState = await checkLoginSession();
return setIsVerified(sessionState);
})();
}, []);
return isVerified ? (
<Route exact={exact} path={path} component={component} />
) : (
<Redirect to={"/login"} />
);
};
Non-protected:
import React from "react";
import { Redirect, Route } from "react-router-dom";
import { useEffect, useState } from "react";
export const NAuthRoute = ({ exact = false, path, component }) => {
const [isVerified, setIsVerified] = useState(false);
const checkLoginSession = () => {
// Write your verifying logic here
const loggedUser = window.localStorage.getItem("accessToken") || "";
return loggedUser !== "" ? true : false;
};
useEffect(() => {
(async function () {
const sessionState = await checkLoginSession();
return setIsVerified(sessionState);
})();
}, []);
return isVerified ?
<Redirect to={"/"} />
: <Route exact={exact} path={path} component={component} />;
};
I usually use this process and so far it is the most helpful way to make a route protected.
ProtectedRoute.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const ProtectedRoute = ({component: Component, ...rest}) => {
const isSessionAuth = sessionStorage.getItem('token');
return (
<Route
{...rest}
render={props => {
if(isSessionAuth)
{
return (<Component {...props}/>);
}else{
return <Redirect to={
{
pathname: "/",
state: {
from: props.location
}
}
}/>
}
}
}/>
);
};
In the place where you are defining routes you can use this like this.
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import { ProtectedRoute } from './ProtectedRoute.js';
import SignUp from "./pages/SignUp";
import SignIn from "./pages/SignIn";
import Dashboard from "./pages/Dashboard";
import NotFound from "./pages/NotFound";
const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={SignIn} />
<ProtectedRoute exact path="/dashboard" component={Dashboard} />
<Route path="/signup" component={SignUp} />
<Route path="*" component={NotFound} />
</Switch>
</BrowserRouter>
);
export default Routes;
I am migrating an app from Firebase to AWS Amplify. I want to create a React context which will provide route protection if the user is not logged in.
For example, my Auth.js file:
import React, { useEffect, useState, createContext } from 'react'
import fire from './firebase'
export const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null)
useEffect(() => {
fire.auth().onAuthStateChanged(setCurrentUser)
}, [])
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
)
}
And my App.js file:
import * as React from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import Navbar from './components/navbar/navbar'
import Home from './routes/Home'
import Register from './routes/Register'
import Footer from './components/footer/Footer'
import AlertProvider from './components/notification/NotificationProvider'
import MyAlert from './components/notification/Notification'
import { AuthProvider } from './Auth'
import PrivateRoute from './PrivateRoute'
const App = () => {
return (
<AuthProvider>
<BrowserRouter>
<AlertProvider>
<div className="app">
<Navbar />
<MyAlert />
<Switch>
<Route path="/" exact component={Home} />
<Route
path="/register"
exact
component={Register}
/>
<Route
path="/forgot-password"
render={(props) => <div>Forgot Password</div>}
/>
<Route path="*" exact={true} component={Home} />
</Switch>
<Footer />
</div>
</AlertProvider>
</BrowserRouter>
</AuthProvider>
)
}
export default App
This all works fine.
How would I do something similar with AWS Amplify? Essentially how would I create a Auth.js file that would wrap around my routes and give them a user context (which would update when the authentication status for the user is changed).
Thanks!
You can achieve this by setting up a custom protectedRoute HOC that will be used to protect any route that requires authentication. It will check if the user is signed-in and if the user is not signed-in then it will re-direct them to a specified route.
protectedRoute.js
import React, { useEffect } from 'react'
import { Auth } from 'aws-amplify'
const protectedRoute = (Comp, route = '/profile') => (props) => {
async function checkAuthState() {
try {
await Auth.currentAuthenticatedUser()
} catch (err) {
props.history.push(route)
}
}
useEffect(() => {
checkAuthState()
})
return <Comp {...props} />
}
export default protectedRoute
You can specify the default route or another route like the following:
// default redirect route
export default protectedRoute(Profile)
// custom redirect route
export default protectedRoute(Profile, '/sign-in')
You could also use the pre-built HOC from aws-amplify called withAuthenticator and that provides the UI as well as checking the users authentication status.
Sample use case for a profile page:
import React, { useState, useEffect } from 'react'
import { Button } from 'antd'
import { Auth } from 'aws-amplify'
import { withAuthenticator } from 'aws-amplify-react'
import Container from './Container'
function Profile() {
useEffect(() => {
checkUser()
}, [])
const [user, setUser] = useState({})
async function checkUser() {
try {
const data = await Auth.currentUserPoolUser()
const userInfo = { username: data.username, ...data.attributes, }
setUser(userInfo)
} catch (err) { console.log('error: ', err) }
}
function signOut() {
Auth.signOut()
.catch(err => console.log('error signing out: ', err))
}
return (
<Container>
<h1>Profile</h1>
<h2>Username: {user.username}</h2>
<h3>Email: {user.email}</h3>
<h4>Phone: {user.phone_number}</h4>
<Button onClick={signOut}>Sign Out</Button>
</Container>
);
}
export default withAuthenticator(Profile)
The routing for both would be the same and below I have linked a sample that I have used for both.:
import React, { useState, useEffect } from 'react'
import { HashRouter, Switch, Route } from 'react-router-dom'
import Nav from './Nav'
import Public from './Public'
import Profile from './Profile'
import Protected from './Protected'
const Router = () => {
const [current, setCurrent] = useState('home')
useEffect(() => {
setRoute()
window.addEventListener('hashchange', setRoute)
return () => window.removeEventListener('hashchange', setRoute)
}, [])
function setRoute() {
const location = window.location.href.split('/')
const pathname = location[location.length-1]
setCurrent(pathname ? pathname : 'home')
}
return (
<HashRouter>
<Nav current={current} />
<Switch>
<Route exact path="/" component={Public}/>
<Route exact path="/protected" component={Protected} />
<Route exact path="/profile" component={Profile}/>
<Route component={Public}/>
</Switch>
</HashRouter>
)
}
export default Router
Good evening everyone,
I have been trying to add withRouter to my react app so it does not break because of the connect function (see code below).
My code is working, but when i add withRouter to the line below, it breaks my app with the following message :
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
Error: Invariant failed: You should not use <withRouter(Connect(App)) /> outside a Router>
i found this topic : Invariant failed: You should not use <Route> outside a <Router> but it's not helping me with me issue when i try to replace with a single Router
Please find below the whole code used :
App.js
import React, {useEffect}from 'react';
import { BrowserRouter } from 'react-router-dom'
import { Route, Redirect} from 'react-router-dom'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from './store/actions/index'
// Composants
import Layout from './components/hoc/Layout/Layout'
import BudgetHandler from './components/BudgetHandler/BudgetHandler'
import DashBoard from './components/DashBoard/DashBoard'
import Movements from './components/Movements/Movements'
import Home from './components/Home/Home'
import Logout from './components/Logout/Logout'
import classes from './App.module.css'
const App = (props) => {
useEffect(() => {
props.onTryAutoSignup()
},[])
let routes = <React.Fragment>
<Route path="/" exact component={Home} />
<Redirect to="/" />
</React.Fragment>
if(props.isAuthentificated) {
routes = <React.Fragment>
<Route path="/movements" component={Movements} />
<Route path="/dashboard" component={DashBoard} />
<Route path="/logout" component={Logout} />
<Route path="/handler" component={BudgetHandler} />
<Redirect to="/dashboard" />
</React.Fragment>
}
return (
<div className={classes.App}>
<BrowserRouter>
<Layout>
{routes}
</Layout>
</BrowserRouter>
</div>
);
}
const mapStateToProps = state => {
return {
isAuthentificated: state.auth.token !== null
}
}
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
And this is happening because i am trying to add this function to the useEffect hook to check permanently if the user is auth or not :
in actions/auth.js
export const authCheckState = () => {
return dispatch => {
const token = localStorage.getItem('token')
if(!token) {
dispatch(logout())
} else {
const expirationTime = new Date(localStorage.getItem('expirationDate'))
const userId = localStorage.getItem('userId')
if(expirationTime > new Date()){
dispatch(logout())
} else {
dispatch(finalSignIn(token, userId))
dispatch(checkAuthTimeout(expirationTime.getSeconds() - new Date().getSeconds()))
}
}
}
}
Thank you for your help
Have a good evening
withRouter can only be used in children components of element. In your case can be used with Movements, DashBoard and other childrens. use it while exporting Movements like
export default withRouter(Movements)
on Movements page.