Is there any sense to useContext in React? - reactjs

Hello i need help with my app.
What sense is to put my User data to useContext if when i refresh page all data disapear?
How to make this context permament?
I have been sitting with this all day, and when i try to get data in HomePage, everything is ok until i refresh page.
The second question is about JWT. It's used only on server side, right? It's verifing the token when I'm making server reqest only?
below is my code
App.js
import Navbar from "./components/Navbar";
import AddNewAnnoucement from "./components/pages/AddNewAnnoucement/AddNewAnnoucement";
import { Route, Routes } from 'react-router-dom';
import Home from "./components/pages/Home";
import Annoucements from "./components/pages/Annoucements/Annoucements";
import Register from "./components/pages/Register/Register";
import Login from "./components/pages/Login/Login";
import { useState, useMemo, useEffect, createContext } from "react";
import { getProfile, tokenIsValid } from './service/userService.js'
export const UserContext = createContext();
function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
})
useEffect(() => {
const isLoggedIn = async () => {
let token = localStorage.getItem("token")
if (token == null) {
localStorage.setItem("token", "")
token = ""
}
const tokenResponse = tokenIsValid(token);
tokenResponse.then((res) => {
if (res.data) {
const userResponse = getProfile(token);
userResponse.then((res) => {
setUserData({
token: token,
data: res.data
})
})
}
}).catch((err) => {
console.log(err);
}
)
}
isLoggedIn();
}, [])
return <>
<UserContext.Provider value={{ userData, setUserData }}>
<Navbar />
<div className='container'>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/annoucements' element={<Annoucements />} />
<Route path='/annoucements/add' element={<AddNewAnnoucement />} />
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
</Routes>
</div>
</UserContext.Provider>
</>
}
export default App;

Related

Accessing url only with valid token

I'm using React and firebase, where I have an admin, and there I generate a token, which I can already register in the database. So I wanted to copy this generated url, for example: localhost:5173/avaliacao/a57078f588e, where a57078f588e is the generated token id for each survey
So the user would access the Rating page only if this token is valid, otherwise it would go to a 404 page.
I'm trying, however everything I try the undefined token.
Here is my App.jsx
export function App() {
return (
<Router>
<Routes>
<Route element={<PrivateRoutes />}>
<Route element={<Admin />} path="/admin" />
<Route element={<Dashboard />} path="/dashboard" />
<Route element={<Collaborator />} path="/collaborators" />
<Route element={<Service />} path="/services" />
</Route>
<Route para element={<TokenRoutes />}>
<Route element={<Rating />} path="/avaliacao/:token" />
<Route element={<Thanks />} path="/thanks" />
<Route element={<NotFound />} path="*" />
</Route>
<Route element={<Login />} path="/" />
</Routes>
</Router>
);
}
And here is my TokenRoutes:
import { Navigate, Outlet } from "react-router-dom";
import { useToken } from "../hooks/useToken";
export function TokenRoutes() {
const { token } = useToken();
console.log(token);
return token != "undefined" ? <Outlet /> : <Navigate to="/notfound" />;
}
And my Rating page:
export function Rating() {
const { token } = useToken();
console.log(token);
let { id } = useParams();
return (
<div className="containerRating">
<span className="login100-form-title p-b-48">
<i className="zmdi zmdi-font"></i>
<img src={imgLogo} alt="Imagem" />
</span>
Param: {id}
<Form />
</div>
);
}
My useToken:
import { addDoc, collection, getDocs } from "firebase/firestore";
import { createContext, useContext, useEffect, useState } from "react";
import { toast } from "react-toastify";
import { uid } from "uid";
import { db } from "../services/firebaseConfig";
const TokenContext = createContext();
export function TokenProvider({ children }) {
const [tokens, setTokens] = useState([]);
const [token, setToken] = useState();
const tokensCollectionRef = collection(db, "tokens");
useEffect(() => {
const getTokens = async () => {
const data = await getDocs(tokensCollectionRef);
setTokens(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getTokens();
}, []);
async function generateToken() {
await addDoc(tokensCollectionRef, {
id: uid(),
createdAt: new Date().toString(),
expiredIn: new Date(
new Date().setDate(new Date().getDate() + 7)
).toString(),
used: false,
})
.then(() => {
toast.success("Avaliação gerada com sucesso!", {
theme: "colored",
});
console.log("Gerado no firebase 🔥");
})
.catch((error) => {
toast.error("Ocorreu um erro ao gerar", {
theme: "colored",
});
console.log(error.message);
});
}
return (
<TokenContext.Provider value={{ generateToken, token, tokens }}>
{children}
</TokenContext.Provider>
);
}
export function useToken() {
const context = useContext(TokenContext);
return context;
}
In order not to put all the code here, any other doubt, follow the complete code on github:
https://github.com/eltonsantos/evaluation-system-react
Resuming:
I just want to get the URL generated by me containing the token, share it with the user and the user was able to access it, but if he changes the url it gives a 404 page. url that I shared the token comes undefined

ReactJS Auth context lost when reloading page

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"

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

How to restrict pages to logged in users only

I want to protect some urls so users can't access them without login in first. This is my Auth.js
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
firebase.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider
value={{ currentUser }}
>
{children}
</AuthContext.Provider>
);
};
And this is my App.jsx
function App() {
return (
<AuthProvider>
<Router>
<div>
<Route path="/citas" exact>
<CitasPage />
</Route>
<Route path="/citasDetalladas" exact>
<CitaDetallada />
</Route>
<Route path="/crearCita" exact>
<CrearCitasPage />
</Route>
<PrivateRoute path="/index" exact >
<Index />
</PrivateRoute>
</div>
</Router>
</AuthProvider>
);
}
export default App;
Since I'm new to React, I've been following a tutorial for doing the login and logout using Firebase and React, but it didn't help with this issue. Any ideas?
You can use this auth flow to protect routes from unauthenticated users.
I have created global context with maintain currentUser.
By this currentUser, routes are decided.
AuthProvider.js
import React, { createContext, useState, useEffect } from "react";
import firebase from "./firebase";
export const AuthContext = createContext();
export function AuthProvider({ children }) {
useEffect(() => {
setIsLoading(true);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setCurrentUser(user);
setIsLoading(false);
} else {
setIsLoading(false);
setCurrentUser(null);
}
});
}, [setCurrentUser, setRole, setIsLoading]);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
App.js
import React from "react";
import { HashRouter as Router } from "react-router-dom";
import { AuthProvider } from "./data/auth";
function App() {
return (
<AuthProvider>
<Router>
<Routes />
</Router>
</AuthProvider>
);
}
export default App;
Routes.js
import React, { useContext } from 'react'
import { Switch, Redirect, Route } from 'react-router-dom';
import { AuthContext } from "../data/auth";
const Routes = () => {
const { currentUser } = useContext(AuthContext);
if (currentUser) {
return (
<Switch>
<Route path='/dashboard' component={dashboard} />
<Route path='/report' component={report} />
<Route path='/profile' component={profile} />
<Redirect to='/report' />
</Switch>
)
} else {
return (
<Switch>
<Route path='/login' component={login} />
<Route path='/register' component={register} />
<Redirect to='/login' />
</Switch>
)
}
}
export default Routes;
You need to do something to determine whether the user is currently authenticated. I'm not sure exactly how firebase.auth() works but you need to check on each route (or use a higher-order component) whether the user is authenticated and if not, return <Redirect to='somewhereElse' />

Multi User login authentication | React Redux

I have an application with multi user login functionality. Now, I created the redux store for the login function. If User logged in, based on their type, it will redirect to the particular dashboard. If student user logged in, he should be able to only access student dashboard, he should not able to access other dashboards. Same applies for others. But, now, if any user logged in, they can able to access other user dashboards. How can i prevent them to use only their dashboards.
Updated With Proper Code
/***AppRouter***/
import React, {Component} from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import StuDashboard from '../views/ed/dashboard/Dashboard.js';
import AdminDashboard from '../views/biz/dashboard/Dashboard.js';
import OrgDashboard from '../views/org/dashboard/Dashboard.js';
import SupAdDashboard from '../views/me/dashboard/Dashboard.js';
import UserLogin from '../views/loginUser/Login.js';
import history from '../history/history.js';
import { PrivateRoute } from './PrivateRoute.js';
import NotFoundPage from '../views/NotFoundPage.js';
import Authorization from './Authorization';
class AppRouter extends Component {
render () {
return(
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard),["Student"]}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard),["Admin"}/>
<PrivateRoute path="/org/dashboard" component={Authorization(OrgDashboard),["Org"]}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard),["SuperAdmin"]}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>
);
}
}
export default AppRouter;
/***PrivateRoute***/
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('token')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
/***Authorization***/
import React, { Component } from 'react';
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) =>{
class WithAuthorization extends Component {
render() {
const userType = this.props.user
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />
} else {
return <h1>You are not allowed to view this page!</h1>
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
}
export default Authorization;
/***Action.js***/
import axios from "axios";
import { LOGIN_PENDING, LOGIN_COMPLETED, LOGIN_ERROR, LOGOUT } from "./types";
import ApiUrl from "../../constants/ApiUrl.js";
import qs from "qs";
import history from '../../history/history.js';
const startLogin = () => {
return {
type: LOGIN_PENDING
};
};
const loginComplete = data => ({
type: LOGIN_COMPLETED,
data
});
const loginError = err => ({
type: LOGIN_ERROR,
err
});
export const loginUser = (email, password) => {
return dispatch => {
dispatch(startLogin());
let headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
const data = qs.stringify({
grant_type: "password",
username: email,
password: password,
});
axios
.post(`${ApiUrl}/Token`, data, {
headers: headers
})
.then(function (resp) {
dispatch(loginComplete(resp.data));
localStorage.setItem("token", resp.data.access_token);
switch (resp.data.userType) {
case 'Admin': {
history.push('/admin/dashboard');
break;
}
case 'Student': {
history.push('/student/dashboard');
break;
}
case 'Organization': {
history.push('/org/dashboard');
break;
}
case 'SuperAdmin': {
history.push('/SuperAdmin/dashboard');
break;
}
default:
history.push('/');
}
window.location.reload();
return;
})
.catch(err => dispatch(loginError(err)));
};
};
export const logOut = () => {
localStorage.clear();
return {
type: LOGOUT,
};
}
I added an Authorization HOC to cater for access control. So you just pass the component, and the allowed role to the Authorization HOC.First i assume your user has a property userType. check out this URL.
/***Authorization***/
import React, { Component } from "react";
import { connect } from "react-redux";
const Authorization = (WrappedComponent, allowedRoles) => {
class WithAuthorization extends Component {
render() {
const userType = this.props.userType;
if (allowedRoles.includes(userType)) {
return <WrappedComponent {...this.props} />;
} else {
return <h1>You are not allowed to view this page!</h1>;
}
}
}
const mapStateToProps = state => ({ user: state.login.userName, userType: state.login.userType })
return connect(mapStateToProps)(WithAuthorization);
};
export default Authorization;
your router will then look this way
<BrowserRouter history={history}>
<Switch>
<Route path="/" component={Landing} exact />
<Route path="/confirmation" component={EmailCon}/>
<PrivateRoute path="/student/dashboard" component={Authorization(StuDashboard,["Student"])}/>
<PrivateRoute path="/admin/dashboard" component={Authorization(AdminDashboard,["Admin"])}/>
<PrivateRoute path="/org/dashboard" component={ Authorization(OrgDashboard,["Organization"])}/>
<PrivateRoute path="/SuperAdmin/dashboard" component={Authorization(SupAdDashboard,["SuperAdmin"])}/>
<Route path="/login" component={UserLogin}/>
<Route path="" component={NotFoundPage} />
</Switch>
</BrowserRouter>

Resources