Protected route depending on their role giving me errors in React - reactjs

I created a AdminProtectedRoute.js inside my frontend that will make sure only admin users will have access to these routes.
AdminProtectedRoute.js
[import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
const AdminProtectedRoute = (props) => {
let MyRender = props.MyRender;
const staff = JSON.parse(localStorage.getItem("staff"));
const history = useNavigate();
const staffRole = staff.role;
useEffect(() => {
if (staffRole !== "admin") {
history.push("/");
}
});
return (
<div>
<MyRender />
</div>
);
};
export default AdminProtectedRoute;][1]
Inside my App.js calling this route protector.
App.js
import {BrowserRouter as Router, Route, Routes} from 'react-router-dom'
import Login from '../src/pages/LoginPage';
import AddStaff from './pages/Admin/AddStaff';
import AdminProtectedRoute from './AdminProtectedRoute';
function App() {
return (
<div className="App">
<Router>
<Routes>
<Route path = "/" element={<Login/>}/>
<Route path = "/add" element= {<AdminProtectedRoute MyRender={<AddStaff/>}/>}></Route>
</Routes>
</Router>
</div>
);
}
export default App;
Error messages

Related

UseContext React: Data doesn't transfer from Login component to SideBar component

I'm trying to use a context in my react project but i dont know why the component "SideBar" can't update the context. The user flow is a user and passwrod simple form in the Login component and if the credentials are valid i want to display the username in the sidebar of my dashboard, as you see this is nothing fancy.
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { UserProvider } from './contexts/user.context';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<UserProvider>
<App />
</UserProvider>
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
App.jsx
import './App.css';
import { Routes, Route } from 'react-router-dom';
import Home from './routes/home/Home.component';
import Login from './routes/auth/Login.component';
import Dashboard from './components/Usuarios/UsuarioValid/MainColumn/Dashboard.component';
import Infrastructura from './components/Usuarios/Anonimo/MainColumn/Infrastructura.component';
import EquipoDirectivo from './components/Usuarios/Anonimo/MainColumn/EquipoDirectivo.component';
import AsignaturasRamas from './components/Usuarios/Anonimo/MainColumn/AsignaturasRamas.component';
const App = () => {
return (
<Routes>
<Route path="/" element={<Login />} />
<Route path="anonimo" element={<Home isAnonimo />} >
<Route index element={<Infrastructura />} />
<Route path="equipo" element={<EquipoDirectivo/>} />
<Route path="asignaturas" element={<AsignaturasRamas/>} />
</Route>
<Route path="dashboard" element={<Home isAnonimo={false} />} >
<Route index element={<Dashboard />} />
</Route>
</Routes>
)
}
export default App;
Home.component.jsx
import React from 'react'
import { Outlet } from 'react-router-dom';
import Sidebar from '../../components/Usuarios/UsuarioValid/Sidebar.component';
import TransitionRoot from '../../components/Usuarios/UsuarioValid/TransitionRoot.component';
import SidebarAnonimo from '../../components/Usuarios/Anonimo/SidebarAnonimo.component';
import TransitionRootAnonimo from '../../components/Usuarios/Anonimo/TransitionRootAnonimo.component';
const Home = (props) => {
const { isAnonimo } = props;
return (
<>
{
isAnonimo ? (
<div>
<TransitionRootAnonimo />
<SidebarAnonimo />
<Outlet />
</div>
)
: (
<div>
<TransitionRoot />
<Sidebar />
<Outlet />
</div>
)}
</>
)
}
export default Home;
user.context.jsx
import { createContext, useState} from "react";
// Actual value we want to access
export const UserContext = createContext({
currentUser: null,
setCurrentUser: (x) => x,
});
// Used in route file (index.js) to handle context
export const UserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const value = { currentUser, setCurrentUser };
return <UserContext.Provider value={value}> {children} </UserContext.Provider>
}
login.component.jsx
import { useState, useContext } from "react";
import { UserContext } from '../../contexts/user.context';
const Login = () => {
const { currentUser, setCurrentUser } = useContext(UserContext)
const [ formFields, setFormFields ] = useState(defaultFormFields);
const { usuario, password } = formFields;
const [ validCredentials, setValidCredentials ] = useState(true);
const handleSubmit = async (event) => {
event.preventDefault();
const usuariValid = usuarios.find(u =>
u.usuario === usuario
&& u.contraseña === password
);
if(usuariValid){
setCurrentUser(usuariValid);
console.log(currentUser)
/* window.location.replace('/dashboard'); */
} else {
resetFormFields();
setValidCredentials(false);
console.info("Credenciales invalidas");
}
};
/*NOT RELEVANT CODE*/
sidebar.component.jsx
*
import { UserContext } from '../../../contexts/user.context'
import React, {useState, useEffect, useContext, Fragment} from 'react'
const Sidebar = () => {
const { currentUser } = useContext(UserContext);
const [idAsignaturas, setIdAsignaturas] = useState([]);
const [nombreAsignaturas, setNombreAsignaturas] = useState([])
useEffect(()=>{ console.log(currentUser},[currentUser]) /*OUTPUT:null*/
/*NOT RELEVANT CODE*/
}*
The code is correct, the problem is that the Context in react is useful for components that are mounted at the same time. In our case we were trying to use the context in components that are not mounted at the same time, because the component "Sidebar" is loaded after the user logs in "Login". So when the user logs in, the pages are reloaded and "Sidebar" is mounted, when this happens the context restarts and loses the values that had been updated in the "Login" component.

React: Custom Hook not passing data to Context hook

I'm triying to pass data from a validator custom hook component to my context.
My custom hook checkDB receive LoginData but fails when trying to pass a destructured variable to another function in my context
import { useContext, useState } from "react";
import validator from "validator";
import { LoginContext } from "./LoginContext";
import users from "./users.js";
const useValidator = () => {
//a bunch of another states and functions working fine
// function who receives data but not passing to another function in context
const checkDB = (loginData) => {
const errorNoFound = false;
try {
const { emailData, passwordData } = loginData;
const found = users.find((user) => user.correo === emailData);
if (found) {
handlerUserLogged(emailData);
return window.location.href = "/";
} else {
return errorNoFound = true;
}
} catch (error) {}
return {
checkDB,
};
};
export default useValidator;
My login context doesn't receive the data from checkDB function neither update states
import React from "react";
import {createContext, useState } from "react";
export const LoginContext = createContext([]);
function LoginContextProvider({ children }) {
const [userData, setUserData] = useState('');
const [isLogged, SetIsLogged] = useState(false);
function handlerUserLogged({emailData}) {
SetIsLogged(true);
alert(isLogged)
return setUserData(emailData);
}
return (
<LoginContext.Provider value={{ userData, isLogged, setUserData, SetIsLogged,handlerUserLogged }}>
{children}
</LoginContext.Provider>
);
}
export default LoginContextProvider;MY app
My app.js
import React from "react";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import HomePage from "./components/HomePage/HomePage";
import LoginPage from "./components/LoginPage/LoginPage";
import RegisterPage from "./components/RegisterPage/RegisterPage";
import LoginContextProvider from "./meta/LoginContext";
import "./App.css";
function App() {
return (
<BrowserRouter>
<LoginContextProvider >
<div className="App">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/crear-cuenta" element={<RegisterPage />} />
<Route path="/*" element={<Navigate to="/" />} />
</Routes>
</div>
</LoginContextProvider>
</BrowserRouter>
);
}
export default App;

Best practise: Where to put the Navbar component so that it -re-renders with every page?

This has been bugging me for a long time, and my head just can't seem to come up with a solution that works fluently. I will try to be short:
I have a React app where I update my Navbar based on an access token being stored as a cookie or not (user authenticated or not). But in order for my Navbar to update whether the user is authenticated, I have to refresh the page. I believe it is because my Navbar is being rendered way up (in?) the component tree, and does not get re-rendered whenever I visit a new page (only when I refresh).
What is the best way to render my Navbar component so that every page shares the same component, BUT the Navbar re-renders on every new page visited? Or is this even a wise thing to do?
Let me know if I am not explaining myself clearly.
index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./components/App";
import { BrowserRouter } from "react-router-dom";
import "./styles/styles.css";
import { PublicClientApplication } from "#azure/msal-browser";
import { MsalProvider } from "#azure/msal-react";
import { msalConfig } from "./lib/authConfig";
const msalInstance = new PublicClientApplication(msalConfig);
ReactDOM.render(
<MsalProvider instance={msalInstance}>
<BrowserRouter>
<App />
</BrowserRouter>
</MsalProvider>,
document.getElementById("root")
);
App.jsx
import React from "react";
import { Router } from "./Router";
import { Navbar } from "./Navbar";
export function App() {
return (
<div>
<Navbar />
<Router />
</div>
);
}
Router.jsx
import { Route } from "react-router-dom";
import React from "react";
import { MainPage } from "./MainPage";
import { LoginPage } from "./LoginPage";
import { GoogleLogin } from "./GoogleLogin";
import { GoogleLoginCallback } from "./GoogleLoginCallback";
import { GoogleLogout } from "./GoogleLogout";
import { NewArticle } from "./NewArticle";
export function Router() {
return (
<div>
<Route exact path="/" component={MainPage} />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/login/google" component={GoogleLogin} />
<Route exact path="/logout/google" component={GoogleLogout} />
<Route exact path="/newArticle" component={NewArticle} />
<Route
exact
path="/login/google/callback"
component={GoogleLoginCallback}
/>
</div>
);
}
Navbar.jsx
import React, { useEffect, useState } from "react";
import { NavLink } from "react-router-dom";
import { useIsAuthenticated, useMsal } from "#azure/msal-react";
import { fetchJSON } from "../lib/fetchJSON";
export function Navbar() {
const activeStyle = {
borderBottom: "10px solid white",
};
const [username, setUsername] = useState();
const { accounts } = useMsal();
const name = accounts[0] && accounts[0].name;
const hasCookie = document.cookie.startsWith("access_token");
const isAuthenticated = useIsAuthenticated();
useEffect(async () => {
const res = await fetchJSON("/api/login/google");
if (hasCookie) {
setUsername(res.userinfo.given_name);
}
}, []);
return (
<>
<div className={"navbar-container"}>
<NavLink exact to="/" activeStyle={activeStyle}>
Home
</NavLink>
<NavLink exact to="/login" activeStyle={activeStyle}>
Login-portal
</NavLink>
{isAuthenticated ? (
<NavLink exact to="/newArticle" activeStyle={activeStyle}>
New Article
</NavLink>
) : (
<div />
)}
{isAuthenticated || hasCookie ? (
<p className="logged-in">
Logged in as {username} {name}
</p>
) : (
<p className="logged-in">Not logged in</p>
)}
</div>
</>
);
}

Protected Routes with AWS Amplify using React context

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

history change url does not load

When you click on the button, the url changes but the redirect does not occur. If you reload manually, then it opens the desired page. Tell me how to fix this problem.
I suspect the problem is with router and history communication.
ROUTE.JS
import React, {Component} from "react";
import Cookies from "universal-cookie";
import history from "../../history";
const {SubMenu} = Menu;
export default class Sider extends Component {
outUser = () => {
const cookies = new Cookies();
cookies.remove("jwt", { path: '/' });
history.push("/");
}
render() {
return (
<div>
<Button onClick={this.outUser}>Выйти</Button>
</div>
);
}
}
APP.JS
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route
} from "react-router-dom";
import history from "./history";
import Auth from "./pages/auth/component"
import HomePage from "./pages/home/component";
import Cookies from 'universal-cookie';
const cookies = new Cookies();
function App() {
return (
<Router history={history}>
<Switch>
<Route exact component={Auth} path="/" />
<div className="App">
<div className="pages">
<Route component={HomePage} path="/home"/>
</div>
</div>
</Switch>
</Router>
);
}

Resources