I am making a simple SPA where you need to login before you can access other pages. I can successfully login and store the login data (firstname, lastname, etc.) cause I plan to use the data again later in the other pages. The problem is whenever I refresh the page, it always empty the state in the context which cause me to return to the login page. I am referring link for my SPA.
Do I need to do this? I would be thankful if someone can point out what I should change / improve. Thank you.
Here is my code.
App.js
import React, { useState } from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import { AuthContext } from "./context/auth";
import PrivateRoute from "./PrivateRoute";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Home from "./pages/Home";
import Admin from "./pages/Admin";
function App() {
const [authTokens, setAuthTokens] = useState();
const setTokens = (data) => {
// console.log("DATA ",data);
localStorage.setItem("tokens", JSON.stringify(data));
setAuthTokens(data);
}
// console.log(authTokens);
return (
<AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
<Router>
<div className="app">
<ul>
<li><Link to="/">Home Page</Link></li>
<li><Link to="/admin">Admin Page</Link></li>
</ul>
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
<Route exact path="/" component={Home} />
<PrivateRoute exact path="/admin" component={Admin} />
</div>
</Router>
</AuthContext.Provider>
);
}
export default App;
Login.js
import React, { useState } from "react";
import axios from "axios";
import { Link, Redirect } from "react-router-dom";
import { useAuth } from "../context/auth";
import { Card, Form, Input, Button, Error } from "../components/AuthForm";
const Login = () => {
const [isLoggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { setAuthTokens } = useAuth();
const handleLogin = () => {
axios
.post("LOGINLINK", {
email,
password,
})
.then((result) => {
if (result.status === 200) {
setAuthTokens(result.data);
setLoggedIn(true);
} else {
setIsError(true);
}
})
.catch((error) => {
setIsError(true);
});
};
if (isLoggedIn) {
return <Redirect to="/" />;
}
return (
<Card>
<Form>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
<Input
type="password"
placeholder="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<Button onClick={handleLogin}>Login</Button>
</Form>
<Link to="/signup">Don't have an account?</Link>
{isError && (
<Error>The username or password provided were incorrect!</Error>
)}
</Card>
);
};
export default Login;
Auth.js
import { createContext, useContext } from "react";
export const AuthContext = createContext();
export function useAuth() {
console.log("CONTEXT", useContext(AuthContext));
return useContext(AuthContext);
}
In your App component you need to fetch the data from localStorage when initializing your state so it has some data to start with.
const localToken = JSON.parse(localStorage.getItem("tokens"));
const [authTokens, setAuthTokens] = useState(localToken);
If user has already authenticated it will be available in localStorage else it's going to be null.
I also had same problem but I solved liked this Don't use localStorage directly use your state and if it is undefined then only use localStorage. cause directly manipulating state with localStorage is in contrast with react internal state and effects re-render .
const getToken = () => {
JSON.parse(localStorage.getItem('yourtoken') || '')
}
const setToken = (token) => {
localStorage.setItem('key' , token)
}
const [authTokens, setAuthTokens] = useState(getToken());
const setTokens = (data) => {
// console.log("DATA ",data);
setToken(token);
setAuthTokens(data);
}
Related
I have problem with react-routerand useEffect. When I want to login user, useEffecshould take userData from cookie, and ˙navigate` user to different component. But it stuck.
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
This is error in console.
I hope you can help me, if you need anything else just let me know.
This is my code from all components.
<Login />
import React, { useState, useEffect } from "react";
import Axios from "axios";
import { useLocation, useNavigate } from "react-router-dom";
import classes from "./Login.module.css";
const Login = ({ setUserData, userData }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const location = useLocation();
Axios.defaults.withCredentials = true;
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
setUserData(response.data);
window.location.reload(true);
});
};
useEffect(() => {
if (userData) {
navigate("/");
}
}, [userData]);
return (
<div className={classes.container}>
<div className={classes.slika}>
<img src="./utilities/scientisc.svg" />
</div>
<div className={classes.login_content}>
<form onSubmit={login}>
<div className={classes.formBorder}>
<img className={classes.avatar} src="utilities/test.png" />
<h2 className={classes.title}>Welcome</h2>
<div /* className={`${classes.input_div} ${classes.one}`}
*/>
<div className={`${classes.inputDiv} ${classes.one}`}>
<h5 className={classes.label}>Korisničko ime</h5>
<input
onChange={(e) => {
setUsername(e.target.value);
}}
type="text"
className={classes.input}
/>
</div>
</div>
<div /* className={`${classes.input_div} ${classes.pass}`}
*/>
<div /* className="div" */>
<h5 className={classes.label}>Lozinka</h5>
<input
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
className={classes.input}
/>
</div>
</div>
<button type="submit" className={classes.button}>
Prijavi se
</button>
</div>
</form>
<h3 className={classes.labelUpozorenja}>{userData?.message}
</h3>
</div>
</div>
);
};
export default Login;
<App /> component
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-
router-
dom";
import Login from "./pages/Login";
import PrivateRoutes from "./utils/PrivateRoutes";
import { useState, useEffect } from "react";
import Axios from "axios";
import SideMenu from "./components/SideMenu/SideMenu";
function App() {
const [userData, setUserData] = useState();
const [checking, setChecking] = useState(true);
useEffect(() => {
Axios.get("http://localhost:3001/login")
.then((response) => {
if (response.data.loggedIn == true) {
setUserData(response.data);
}
return;
})
.catch((error) => {
console.log(error);
})
.finally(() => {
setChecking(false);
});
}, []);
const handleClick = async () => {
try {
await Axios.post("http://localhost:3001/logout", {
name: "userId",
});
window.location.reload(true);
} catch (error) {
console.error(error);
}
};
return (
<div className="App">
<Router>
<Routes>
<Route
element={<PrivateRoutes userData={userData} checking=
{checking} />}
>
<Route element={<SideMenu handleClick={handleClick}
userData={userData} />} path="/" exact />
{/* <Route element={<Products />} path="/products" />
*/}
</Route>
<Route
element={<Login setUserData={setUserData} userData=
{userData} />}
path="/login"
/>
</Routes>
</Router>
</div>
);
}
export default App;
<PrivateRoutes />
import { Outlet, Navigate } from "react-router-dom";
import React from "react";
const PrivateRoutes = ({ userData, checking }) => {
return checking ? (
<p>Checking...</p>
) : userData?.loggedIn ? (
<Outlet />
) : (
<Navigate to="/login" />
);
};
export default PrivateRoutes;
You are using wrong approach for this, why arent you using navigate() in login ?
const login = (e) => {
e.preventDefault();
Axios.post("http://localhost:3001/login", {
username: username,
password: password,
}).then((response) => {
navigate('/')
});
};
This will redirect the user after successful login.
UPDATE -
Noted that you said in refreshing case it would fail.
Why are you using that e in login? Login doesn't really need an Event I guess !
May be that (e) is preventing the app from refreshing the state! can you try without it ?
Only redirect if the path is /login. This will not cause an infinite loop because it will not redirect unless the path is /login
import { useLocation, useNavigate } from "react-router-dom";
const location = useLocation();
useEffect(() => {
if (userData && location.pathname === "/login") {
navigate("/");
}
}, [userData, location.pathname]);
Note: your login in path might be different, if so just swap out "/login" to the path you're using.
I am making a site whereby after the user signs in, the user is meant to be redirected to the home page. The homepage and all the other pages of the site are only accessible by signed in users but even after a user signs in(firebase auth), the rest of the site(protected routes) is still not accessible and the only page accessible is the login page. The technologies I am using are react, react router dom and firebase and this is how my code looks like, starting with the App.js
import Home from "./pages/home/Home";
import Login from "./pages/login/Login";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import List from "./pages/list/List";
import User from "./pages/user/User";
import AddNew from "./pages/addnew/AddNew";
import { useContext } from "react";
import { AuthContext } from "./context/AuthContext";
function App() {
const {currentUser} = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/login" exact element={<Login />} />
<Route path="/" exact element={ <RequireAuth> <Home /> </RequireAuth> } />
<Route path="/users" exact element={<RequireAuth><List /></RequireAuth>} />
<Route path="/users/:id" exact element={<RequireAuth><User /></RequireAuth>} />
<Route path="/add" exact element={<RequireAuth><AddNew /></RequireAuth>} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
And then followed by the login.js page
import React,{useState} from 'react'
import { useContext } from 'react';
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from '../../firebase';
import "./login.css";
import {useNavigate} from "react-router-dom";
import { AuthContext } from '../../context/AuthContext';
export default function Login() {
const [error, seterror] = useState(false);
const [email, setemail] = useState("");
const [password, setpassword] = useState("");
const navigate = useNavigate();
const {dispatch} = useContext(AuthContext)
const handleLogin = (e) => {
e.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
dispatch({type: "LOGIN", payload: user});
navigate("/");
})
.catch((error) => {
seterror(true);
console.log(error.message);
});
}
return (
<div className='login'>
<form onSubmit={handleLogin}>
<input className='ok' type="email" placeholder='email' onChange={e => setemail(e.target.value)} />
<input className='ok' type="password" placeholder='password' onChange={e => setpassword(e.target.value)} />
<button className='sb'>Submit</button>
{error && <span className='ks'>Wrong email or password</span>}
</form>
</div>
)
}
And then I have the authreducer.js file that deals with the state
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN": {
return {
currentUser: action.payload,
}
}
case "LOGOUT": {
return {
currentUser: null
}
}
default:
return state;
}
}
export default AuthReducer
And finally the authcontext.js file
import { createContext, useEffect, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const INITIAL_STATE = {
currentUser: JSON.parse(localStorage.getItem("user")) || null,
}
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.currentUser))
}, [state.currentUser])
return (
<AuthContext.Provider value={{current: state.current, dispatch}}>
{children}
</AuthContext.Provider>
)
}
I do not know what could be causing this problem but I have an idea that it has something to do with the state because it was redirecting well before I started combining it with the state. What could be the problem
Issue
From that I can see, the App isn't destructuring the correct context value to handle the conditional route protection.
The AuthContextProvider provides a context value with current and dispatch properties
<AuthContext.Provider value={{ current: state.current, dispatch }}>
{children}
</AuthContext.Provider>
but App is accessing a currentUser property, which is going to be undefined because state.current is undefined.
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
The Navigate component will always be rendered.
Solution
Assuming the handleLogin handler correctly updates the state then the solution is to be consistent with state properties.
<AuthContext.Provider value={{ currentUser: state.currentUser, dispatch }}>
{children}
</AuthContext.Provider>
...
const { currentUser } = useContext(AuthContext);
const RequireAuth = ({ children }) => {
return currentUser ? children : <Navigate to="/login" />;
};
I need to restrict links in react router by specific user roles (I have roles stored in token). What I'm trying to do now is:
send username & password through SignIn component to getTokens() function from custom useAuth hook on submit to then pass a boolean isModerator inside route value to ensure that the user have the required authorities for the link to show. In my case request is just not going to the server on form submit, probably because I misuse context api or react itself somehow.
So this is how my useAuth hook looks right now:
import React, { useState, createContext, useContext, useEffect } from "react";
import axios from "axios";
export const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [authed, setAuthed] = useState(false);
const [moderator, setModerator] = useState(false);
const [accessToken, setAccessToken] = useState("");
const [refreshToken, setRefreshToken] = useState("");
const [authorities, setAuthorities] = useState([]);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const signIn = async (e, username, password) => {
e.preventDefault();
const result = await getTokens(username, password);
if (result) {
console.log("User has signed in");
setAuthed(true);
}
};
const isModerator = async () => {
const result = await getAccessTokenAuthorities();
if (result) {
console.log("User is admin");
setModerator(true);
}
};
const getTokens = async (username, password) => {
const api = `http://localhost:8080/api/v1/public/signIn?username=${username}&password=${password}`;
const res = await axios.get(api, {
withCredentials: true,
params: {
username: username,
password: password,
},
});
const data = await res.data;
setAccessToken(data["access_token"]);
setRefreshToken(data["refresh_token"]);
console.log(data);
return accessToken, refreshToken;
};
const getAccessTokenAuthorities = async () => {
const api = `http://localhost:8080/api/v1/public/getAccessTokenAuthorities`;
const res = await axios.get(api, {
withCredentials: true,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await res.data;
setAuthorities(data);
let vals = [];
authorities.forEach((authority) => {
vals.push(Object.values(authority));
});
const check = vals.filter((val) => val.toString() === "MODERATOR");
if (check.length > 0) return !isModerator;
console.log(authorities);
return isModerator;
};
return (
<AuthContext.Provider
value={{
authed,
setAuthed,
moderator,
setModerator,
getTokens,
getAccessTokenAuthorities,
username,
password,
setUsername,
setPassword,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
And this is me trying to use AuthContext in SignIn component:
import React, { useContext, useEffect, useState } from "react";
import { useAuth } from "../hooks/useAuth";
import { AuthContext } from "../hooks/useAuth";
const SignIn = (props) => {
const auth = useAuth();
const userDetails = useContext(AuthContext);
return (
<>
<h1>Вход</h1>
<form
method="get"
onSubmit={(e) => auth.signIn(e)}
encType="application/json"
>
<label htmlFor="username">Имя пользователя</label>
<input
type="text"
id="username"
onChange={(e) => userDetails.setUsername(e.target.value)}
></input>
<label htmlFor="password">Пароль</label>
<input
type="password"
id="password"
onChange={(e) => userDetails.setPassword(e.target.value)}
></input>
Вход
<input type="submit"></input>
</form>
</>
);
};
SignIn.propTypes = {};
export default SignIn;
Here is how I set my AuthProvider in index.js:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css"; import reportWebVitals from "./reportWebVitals";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Inventory from "./components/Inventory";
import SignIn from "./components/SignIn";
import { AuthProvider } from "./hooks/useAuth";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/"
element={<App />}>
</Route>
<Route path="api/v1/public/signIn"
element={<SignIn />}>
</Route>
<Route path="api/v1/moderator/inventory" element={<Inventory />} >
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode> );
reportWebVitals();
Thanks in advance.
You're creating 2 instances of context when you initialize 2 variables of it.
Both of these variables are behaving as separate instances of context. Its like when an object is initialized from a constructor.
All the methods you've passed to context Provider are available on const auth=useAuth(). Inside your Signin component, you're calling userDetails.setUsername() for changing value of username and to submit the form you're calling auth.signin.
You can simply use auth.setUsername(e.target.value) and auth.setPassword(e.target.value). For submitting form use auth.signin()
const SignIn = (props) => {
const auth = useAuth();
// const userDetails = useContext(AuthContext); No need to initialize this one
useEffect(() => {
// here in this log you will see all the methods are available from context provider
if (auth) console.log(auth);
}, [auth]);
return (
<>
<h1>Вход</h1>
<form
method="get"
onSubmit={(e) => auth.signIn(e)}
encType="application/json"
>
<label htmlFor="username">Имя пользователя</label>
<input
type="text"
id="username"
onChange={(e) => auth.setUsername(e.target.value)}
></input>
<label htmlFor="password">Пароль</label>
<input
type="password"
id="password"
onChange={(e) => auth.setPassword(e.target.value)}
></input>
Вход
<input type="submit"></input>
</form>
</>
);
};
export default SignIn;
I'm creating a ProtectedRoute component in React that will take a user state variable as prop.
This user is from my checkUser() func using amplify's Auth.currentAuthenticatedUser().
function App() {
const [user, setUser] = useState();
const { Auth, Hub } = useContext(AmplifyContext)
async function checkUser() {
try {
const loggedInUser = await Auth.currentAuthenticatedUser();
setUser(loggedInUser);
console.log(loggedInUser);
// get null first time?
} catch(e) {
setUser(null);
console.log(e.message);
}
}
useEffect(() => {
checkUser();
}, [Auth])
return (
<Router>
<Suspense fallback={<p>...loading...</p>}>
<Switch>
<IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.LOGIN}>
<Route path={ROUTES.LOGIN} component={Login} />
</IsUserLoggedIn>
<IsUserLoggedIn user={user} loggedInPath={ROUTES.DASHBOARD} path={ROUTES.SIGN_UP}>
<Route path={ROUTES.SIGN_UP} component={SignUp} />
</IsUserLoggedIn>
<ProtectedRoute user={user} path={ROUTES.DASHBOARD} exact>
<Route path={ROUTES.DASHBOARD} exact component={Dashboard} />
</ProtectedRoute>
<Route path={ROUTES.RESET_PW} component={ResetPw} />
<Route component={NoPage} />
</Switch>
</Suspense>
</Router>
);
}
// Protected Route Component
import { Route, Redirect } from "react-router-dom";
import * as ROUTES from '../constants/routes';
export default function ProtectedRoute({user, children, ...restProps}) {
console.log(user);
return (
<Route
{...restProps}
render={({location}) => {
if(user) {
return children;
}
if(!user) {
return (
<Redirect
to={{
pathname: ROUTES.LOGIN,
state: { from: location }
}}
/>
)
}
return null;
}}
/>
)
}
// login component
import { useState, useContext } from "react";
import { Link } from "react-router-dom";
import { useHistory } from 'react-router';
import AmplifyContext from "../context/amplify";
import * as ROUTES from '../constants/routes';
export default function Login() {
const { Auth, Hub } = useContext(AmplifyContext);
const history = useHistory();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const invalid = !username || !password;
const handleLogin = async (e) => {
e.preventDefault();
try {
// amplify Auth login
await Auth.signIn(username, password);
history.push(ROUTES.DASHBOARD);
console.log('logged in');
} catch(e) {
setError(e.message);
setPassword('');
}
}
return (
<div className="auth-container">
<h2 className="auth-title">Log In</h2>
<div className="login-form-container">
<form className="form login-form" onSubmit={handleLogin}>
<input autoFocus type="text" placeholder="username" aria-label="username" value={username} onChange={({target}) => setUsername(target.value)} />
<input type="password" placeholder="password" aria-label="password" value={password} onChange={({target}) => setPassword(target.value)} />
{error && (<p style={{color: 'red'}}>{error}</p>)}
<div className="form-action-container">
<div className="button-container">
<button disabled={invalid} className="form-button" type='submit'>Log In</button>
<p>Need an Account? <span><Link to={ROUTES.SIGN_UP}>Sign Up</Link></span></p>
</div>
<p>Forget your password? <span><Link to={ROUTES.RESET_PW}>Reset</Link></span></p>
</div>
</form>
</div>
</div>
)
}
The current problem is that the useEffect (or maybe the Auth method?) isn't updating the state and so the first time I click "login" in my login component, it returns 'null' from my protectedRoute component's as well as the main App component's console.log(user), returning null. Only after I refresh, does it change and let me get the user log as well as directed into the protectedRoute.
This is also true for my logOut scenario.
export default function Dashboard() {
const { Auth, Hub } = useContext(AmplifyContext);
const history = useHistory();
const handleLogOut = async (e) => {
e.preventDefault();
// amplify call to sign out
await Auth.signOut();
history.push(ROUTES.LOGIN);
}
return (
<div className="dashboard-container">
<h1>Welcome </h1>
<button onClick={handleLogOut}>log out</button>
</div>
)
}
I don't get logged out, nor do I get redirected unless I reload the page.
Why aren't the Auth.signOut() and the Auth.currentAuthenticatedUser() methods run like I want it to?
made her work a bit better after putting all auth related state into context provider and wrapping it around all {children} components and then using Hub to listen for changes to log out. (I had to stop using my route helper functions, so that's kind of a bummer. But it works at the moment. I will keep it as the solution). I had to use the amplify guideline for custom auth, so still not so satisfied...
Each related state variables that needs to be used in each component is using it from context (left it out for length).
// storing all state related to Authorization
import { createContext, useContext, useState } from "react";
import AmplifyContext from "./amplify";
const AuthContext = createContext();
function AuthContextProvider({ children }) {
const [formType, setFormType] = useState("signUp");
const [fullName, setFullName] = useState("");
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [authCode, setAuthCode] = useState("");
const [error, setError] = useState("");
const [user, setUser] = useState(null);
const { Auth } = useContext(AmplifyContext);
let invalid;
const checkUser = async () => {
try {
const loggedInUser = await Auth.currentAuthenticatedUser();
setUser(loggedInUser);
console.log(user);
if (user) {
setFormType("dashboard");
} else {
setUser(null);
setFormType("login");
}
} catch (e) {
console.log(e.message);
}
};
const handleSignUp = async (e) => {
e.preventDefault();
try {
// amp auth signup. attribute must match (ie: if email is needed, state var needs to be called email (not other name))
await Auth.signUp({ username, password, attributes: { email } });
console.log("signed up");
setFullName("");
setUsername("");
setEmail("");
setPassword("");
setFormType("confirmSignUp");
} catch (e) {
console.log(e.message);
setError(e.message);
}
};
const handleConfirmAuthCode = async (e) => {
e.preventDefault();
try {
// amp auth confirm sign up
await Auth.confirmSignUp(username, authCode);
setFormType("login");
} catch (e) {
console.log(e.message);
setError(e.message);
}
};
const handleLogin = async (e) => {
e.preventDefault();
try {
// amplify Auth login
await Auth.signIn(username, password);
setUsername("");
setPassword("");
console.log("logged in");
setFormType("dashboard");
} catch (e) {
setError(e.message);
setPassword("");
}
};
const handleLogOut = async (e) => {
e.preventDefault();
// amplify call to sign out
await Auth.signOut();
//set some loading or redirect?
};
return (
<AuthContext.Provider
value={{
error,
setError,
handleSignUp,
checkUser,
handleConfirmAuthCode,
handleLogin,
handleLogOut,
fullName,
setFullName,
username,
setUsername,
email,
setEmail,
password,
setPassword,
formType,
setFormType,
authCode,
setAuthCode,
invalid,
user,
setUser,
}}
>
{children}
</AuthContext.Provider>
);
}
export { AuthContextProvider, AuthContext };
// top
ReactDOM.render(
<AmplifyContext.Provider value={{ Auth, Hub }}>
<AuthContextProvider>
<App />
</AuthContextProvider>
</AmplifyContext.Provider>,
document.getElementById("root")
);
// inside the App component (not yet finished)
import { useContext, useEffect } from "react";
import AmplifyContext from "./context/amplify";
import { AuthContext } from "./context/AuthContext";
import ConfirmSignUp from "./pages/confirmSignUp";
import Login from "./pages/login";
import SignUp from "./pages/sign-up";
import Dashboard from "./pages/dashboard";
import ResetPass from "./pages/reset-pw";
function App() {
const { Hub } = useContext(AmplifyContext);
const {
formType,
setFormType,
username,
setUsername,
error,
setError,
checkUser,
handleLogOut,
} = useContext(AuthContext);
async function setAuthListener() {
Hub.listen("auth", (data) => {
switch (data.payload.event) {
case "signIn":
console.log(`${username} signed in`);
break;
case "signOut":
console.log("user signed out");
setFormType("login");
break;
default:
break;
}
});
}
useEffect(() => {
checkUser();
setAuthListener();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{formType === "signUp" && <SignUp />}
{formType === "confirmSignUp" && <ConfirmSignUp />}
{formType === "login" && <Login />}
{formType === "dashboard" && (
<Dashboard handleLogOut={handleLogOut} />
)}
{formType === "reset" && (
<ResetPass />
)}
</>
);
}
export default App;
I learn a bit of React. It's time to login for users. But there was a problem.
To get started, the code:
App.js
import React, {useState} from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import PrivateRoute from './PrivateRoute';
import Home from './Home';
import Login from "./Login";
import { AuthContext } from "./Auth";
function App(props) {
const [authTokens, setAuthTokens] = useState(localStorage.getItem("tokens") || "");
const setTokens = (data) => {
localStorage.setItem("tokens", JSON.stringify(data));
setAuthTokens(data);
}
return (
<AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
</ul>
<PrivateRoute exact path="/" component={Home} />
<Route path="/login" component={Login} />
</div>
</Router>
</AuthContext.Provider>);
}
export default App;
Login.js
import React, { useState } from "react";
import { Link, Redirect } from 'react-router-dom';
import axios from 'axios';
import { useAuth } from "./Auth";
function Login(props) {
const [isLoggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [userName, setUserName] = useState("");
const [password, setPassword] = useState("");
const { setAuthTokens } = useAuth();
const referer = props.location.state ? props.location.state.referer : '/';
function postLogin() {
axios.post("https://myapi.com/login.php", {
userName,
password
}).then(result => {
if (result.status === 200) {
setAuthTokens(result.data);
setLoggedIn(true);
console.log(result.data);
} else {
setIsError(true);
}
}).catch(e => {
setIsError(true);
console.log(e);
});
}
if (isLoggedIn) {
return <Redirect to={referer} />;
}
return (
<input type="username" value={userName} onChange={e=>{ setUserName(e.target.value); }} placeholder="username"
/>
<input type="password" value={password} onChange={e=>{ setPassword(e.target.value); }} placeholder="password"
/>
<input type="submit" onClick={postLogin}>Sign In</Button>
{ isError&& <div>The username or password provider were incorrect.</div>}
);
}
export default Login;
Auth.js
import { createContext, useContext } from 'react';
export const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
PrivateRoute.js
import React from 'react'
import { Redirect, Route } from 'react-router-dom'
import { useAuth } from "./Auth";
const PrivateRoute = ({ component: Component, ...rest }) => {
const { authTokens } = useAuth();
return (
<Route
{...rest}
render={props =>
authTokens ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/login", state: { referer: props.location } }} />
)
}
/>
);
}
export default PrivateRoute
At login, everything works well. But when I try to log in to /login again, it doesn’t throw me back to the referer page. Although it should, otherwise why would the user once again see the login page, if he is already logged in.
Is it possible to store LocalStorage, such as cookies, for 30 days?
If possible, please explain the logic of logging in to react.
I understand it this way (correct if I am mistaken):
user enters login and password
in response, I send a TOKEN (if the username and password on the server side match)
I load this token, for example in Mysql, store it there.
the next time I log on to the site, I check if there is a token in the Mysql database (if so, leave it logged in - if not, then delete the row from the database and throw it on the login page).
Right?)
Right :
when you submit your login credentials like username and password then you are getting token from your server then you have to store your token in your local-storage or as cookie like token : Your_token
Then in your router in your App.js check if you are having token or not like
{
!localStorage.getItem('token') ? <Redirect from='/' to='/login' /> : ''
}
Here if you don't have your token then you are redirecting to login else redirecting to your following page.
You can use your token as header in api calls and then you can check if your token is valid or not from your back-end if not then return with error message.
If your token is expire then you can set your token : ' ' in localstorage .Then you can redirect to login page from that api response.
Main thing is token create token which automatically expire as per your requirement and use it with local-storage or cookie.