I met a problem which I cannot solve.. I have register, login authentification and logout. So I noticed that if I refresh my page user is no longer logged in. So I found a solution for it, is using localstorage, of course there's more option how to solve it, but this one is more understandable to me. But as I expected I'm stuck with it, cause of useEffect() which is not my strongest side. So I hope you guys can help me with that. Of course I tried it without it, but than got some "red" color in my code (errors).
There's my code:
This is what I'm using in App.js. There I'm creating a variable window.localStorage with getItem and adding it to MainContext:
function App() {
// Use states
const [user, setUser] = useState(null);
const [message, setMessage] = useState('');
const isLoggedIn = window.localStorage.getItem('isLoggedIn')
// Main context
const states = { user, setUser, message, setMessage, isLoggedIn };
// Side effects
return (
<div className='App'>
<MainContext.Provider value={states}>
<BrowserRouter>
<Routes>
<Route path='/' element={isLoggedIn ? <HomePage /> : <LoginPage />}></Route>
{!user && <Route path='/register' element={<RegisterPage />}></Route>}
{!user && <Route path='/login' element={<LoginPage />}></Route>}
</Routes>
</BrowserRouter>
</MainContext.Provider>
</div>
);
}
export default App;
This is my Login component at the end of login I'm setting localstorage when Login button is clicked:
import React, { useContext, useRef } from 'react';
import MainContext from '../context/MainContext';
import { post } from '../helper/helper';
import { useNavigate } from 'react-router-dom';
export default function Login() {
const nav = useNavigate()
const { message, setMessage, setUser } = useContext(MainContext);
// Setting inputs references
const usernameRef = useRef();
const passwordRef = useRef();
const login = async () => {
// Setting a user for sign in, taking input values
const user = {
username: usernameRef.current.value,
password: passwordRef.current.value,
};
// Posting sign in user to database, if all fields validated (successfully signin), if failed (getting error message)
const result = await post(user, 'login');
if (!result.error) {
setMessage(result.message);
usernameRef.current.value = '';
passwordRef.current.value = '';
setUser(result.data);
} else {
setMessage(result.message);
}
window.localStorage.setItem('isLoggedIn', true)
nav('/')
};
return (
<div className='login'>
<h2>Sign In!</h2>
{message !== '' && <p className='signin__message'>{message}</p>}
<input ref={usernameRef} onClick={() => setMessage('')} type='text' placeholder='Enter Username' />
<input ref={passwordRef} onClick={() => setMessage('')} type='password' placeholder='Enter Password' />
<button onClick={login}>Sign In</button>
</div>
);
}
In Toolbar(header) component I wanna show is user is logged in, than I will show his username and avatar picture in right corner with logout button near it. If user not logged in or logout than in that place will be Login and Register buttons.
{isLoggedIn && <UserTrigger />}
{!isLoggedIn && <NoUser />}
There's user logout logic. If user click on logout button, than window.localStorage.removeItem
import React, { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import MainContext from '../context/MainContext';
export default function UserTrigger() {
const { user, setMessage, setUser } = useContext(MainContext);
const nav = useNavigate();
return (
<div className='toolbar__user'>
<div className='user'>
<img src={user.image} alt="" />
<h2>{user.username}</h2>
</div>
<div className='toolbar__logout'>
<button onClick={() => { setMessage(''); setUser(null); nav('/'); window.localStorage.removeItem('isLoggedIn') }}>
Logout
</button>
</div>
</div>
);
}
I wanna say sorry for that much code. But I wanna make sure that you'll understand it well. And thank you for any answers! <3
Related
I'm learning reactjs and I'm trying to simulate an "Authentication" method on the front-end with reactjs and json-server and I'm facing a problem.
I have theses components:
Apps.js (with all the Routes)
Login.jsx ( with a form and all the logic )
ProtectedRoutes.jsx (as a function component to do a simple verification if the user is logged or no, and protected routes.
Clients.jsx (with all the lists fetched from the json-server, working properly, not important here)
I would like to create one state (isLogged / setIsLogged) to be trigged as "true" when the user hit the submit on my "Login.jsx", reusing this component, since the current state is "false". But I'm not figuring out how to do it. I'm not understanding how I can access the functions / state to do this.
App.js
import "./App.css";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Clients from "./pages/Clients";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import ProtectedRoutes from "./components/ProtectedRoutes";
function App() {
return (
<>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route element={<ProtectedRoutes isLogged={false} />}>
<Route path="/register" element={<Register />} />
<Route path="/clients" element={<Clients />} />
</Route>
</Routes>
</Router>
</>
);
}
export default App;
ProtectedRoutes.jsx
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoutes = ({ isLogged }) => {
return isLogged ? <Outlet /> : <Navigate to="/" />;
};
export default ProtectedRoutes;
Login.jsx
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { validateEmail, validatePassword } from "../utils/regex";
import logoImg from "../assets/logo-troupe.png";
import Navbar from "../components/Navbar";
const Login = () => {
const navigateTo = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailErr, setEmailErr] = useState(false);
const [passwordErr, setPasswordErr] = useState(false);
// input login validations
const validateEmailPassword = () => {
if (!validateEmail.test(email)) {
setEmailErr(true);
} else {
setEmailErr(false);
}
if (!validatePassword.test(password)) {
setPasswordErr(true);
} else {
setPasswordErr(false);
navigateTo("/clients");
}
};
//function to generate a random "token" simulating a login on backend
const handleStorageToken = () => {
const userToken = {
email,
password,
};
localStorage.setItem("Token", JSON.stringify(userToken));
};
const handleSubmit = (e) => {
e.preventDefault();
validateEmailPassword();
handleStorageToken();
};
return (
<div className="main-container">
<Navbar />
<div className="login-container">
<a href="#">
<img src={logoImg} alt="logo" tooltip="Troupe website" />
</a>
<h1>Login</h1>
<form>
<div className="form-group">
<label>E-mail</label>
<input
type="email"
placeholder="Enter your e-mail"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{emailErr && (
<p className="validation-error">
Insira um e-mail válido!
</p>
)}
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{passwordErr && (
<p className="validation-error">
Senha inválida! Mínimo 4 caracteres, 1 letra and 1
número
</p>
)}
<button onClick={handleSubmit} className="btn-login" type="submit">
Login
</button>
</div>
</form>
</div>
</div>
);
};
export default Login;
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);
}
Im building this app for a shop, I have the Login already working but I want that when Login in app, it shows the Sidebar, and when choosing an option, the Sidebar may not dissapear only the content part should change, but it doesn't, heres what I have:
App.js
import React from 'react';
import Login from './components/Login'
import Dashboard from './components/Dashboard';
import Administrar from './components/Productos/Administrar';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
function App() {
return (
<React.Fragment>
<Router>
<Switch>
<Route path="/" exact render={ props => (<Login {...props} />)}></Route>
<Route path="/dashboard" exact render={ props => (<Dashboard {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
);
}
export default App;
Dashboard.jsx
import React, { useState, useEffect} from 'react';
import Sidebar from '../template/Sidebar';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import axios from 'axios';
import {ApiUrl} from '../services/apirest'
import Administrar from './Productos/Administrar'
const SideAndNavbar = () => {
return (
<React.Fragment>
<Sidebar/>
<Router>
<Switch>
<Route path="/productos/administrar" exact component={ props => (<Administrar {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
)
}
const Relog = () => {
return (
<div>
<h1>Relogeate pá</h1>
</div>
)
}
export default function Dashboard() {
const [user, setUser] = useState(null)
useEffect(() => {
obtenerUsuario()
}, [])
const obtenerUsuario = async () => {
let url = ApiUrl + "/usuario";
await axios.get(url)
.then(response => {
setUser(response.data.user)
}).catch(error => {
console.log(error)
})
}
return (
(user ? <SideAndNavbar/>: <Relog/>)
);
}
Login.jsx
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import toast, {Toaster} from 'react-hot-toast'
import logo from '../assets/img/img-01.png'
import axios from 'axios'
import {ApiUrl} from '../services/apirest'
class Login extends Component {
// eslint-disable-next-line
constructor(props){
super(props);
}
state = {
form:{
"email": "",
"password": ""
},
}
manejadorChange = async(e) =>{
await this.setState({
form: {
...this.state.form,
[e.target.name]: e.target.value
}
})
}
manejadorBoton = () => {
let url = ApiUrl + "/auth/logearse";
axios.post(url, this.state.form)
.then(response => {
if(response.data.status === "OK"){
localStorage.setItem("token", response.data.token);
/*
this.props.history.push({
pathname: '/dashboard',
state: response.data.user
})
*/
this.props.history.push('/dashboard');
}else{
toast.error(response.data.message);
}
}).catch(error => {
console.log(error);
toast.error("Error al conectarse con el servidor");
})
}
manejadorSubmit(e){
e.preventDefault();
}
render() {
return (
<React.Fragment>
<Toaster position="top-center" reverseOrder={false}/>
<div className="limiter">
<div className="container-login100">
<div className="wrap-login100">
<div className="login100-pic">
<img src={logo} alt="Imagen"/>
</div>
<form className="login100-form validate-form" onSubmit={this.manejadorSubmit}>
<span className="login100-form-title">
Ingreso de Usuario
</span>
<div className="wrap-input100 validate-input" data-validate = "Valid email is required: ex#abc.xyz">
<input className="input100" type="text" name="email" placeholder="Email" onChange={this.manejadorChange}/>
<span className="focus-input100"></span>
<span className="symbol-input100">
<i className="fa fa-envelope" aria-hidden="true"></i>
</span>
</div>
<div className="wrap-input100 validate-input" data-validate = "Password is required">
<input className="input100" type="password" name="password" placeholder="Password" onChange={this.manejadorChange}/>
<span className="focus-input100"></span>
<span className="symbol-input100">
<i className="fa fa-lock" aria-hidden="true"></i>
</span>
</div>
<div className="container-login100-form-btn">
<button className="login100-form-btn" onClick={this.manejadorBoton}>
Ingresar
</button>
</div>
<div className="text-center p-t-56">
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default Login;
What can I do? Here is some pics:
enter image description here
Looks like you are trying to achieve some heavy Routing without the help of any state management, There are few anti patterns in your current setup. I have a few suggestions that will help you achieve what you are trying to implement.
Firstly Your application routing is setup in a ambiguous way. If you see the routing implementation looks like a nested Routing setup. but the URl you are used is /productos/administrar this route in terms of relativity is rendered from the / base URL.
Your Home(Entry point of the application APP.js) is setup with a parent Router, The router now reads your URL and Tries to render a component. but sees that in the parent Route there is no such URL.
Now if you see carefully your dashboard component is setup to render <SideNav>, to render the sidenav on the /productos/administrar route you should firstly go through the dashboard component.
in your current setup the dashboard component is missed and directly the Admin component is rendered at the root of the router.
I would want you to follow the Layout Pattern where you can stuff the sidenav and topnav (if you have any) along with a main section which render the childrens passed to the component, and on each route call this Layout Component with children props.
this way you ensure the Layout is visible on every route. But that's a long way . If you want a quick fix is to implement proper nested Routing using useRouterMatch() to pass the current route down the component tree. Now change the dashboard component to something like this
const SideAndNavbar = ({match}) => {
return (
<React.Fragment>
// Make sure you user the match props in Sidebar to append he correct URl for nesting to the Link tag , for example (<Link to={`${match.url}/productos/administrar`}>GO to Admin</Link>)
<Sidebar match={match}/>
<Router>
<Switch>
<Route path={`${match.path}/productos/administrar`} exact component={ props => (<Administrar {...props} />)}></Route>
</Switch>
</Router>
</React.Fragment>
)
}
const Relog = () => {
return (
<div>
<h1>Relogeate pá</h1>
</div>
)
}
export default function Dashboard() {
const [user, setUser] = useState(null)
let match = useRouteMatch();
useEffect(() => {
obtenerUsuario()
}, [])
const obtenerUsuario = async () => {
let url = ApiUrl + "/usuario";
await axios.get(url)
.then(response => {
setUser(response.data.user)
}).catch(error => {
console.log(error)
})
}
return (
(user ? <SideAndNavbar match={match} />: <Relog/>)
);
}
For more info on this topic see the official documentation of React Router
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.
I have setup a login route in Express, which when used, it will set the global state of my application (using Context API), to include the users ID and a token, generated by JSONWebToken.
To be able to load todo items for the user, you need to have a valid token, which works when I'm sending it in the headers while doing the API request. The state is also being updated when doing this with Axios, but the problem is that I don't know how (or the best way for how) to forward to the protected route.
Here is my App.js, which includes the routes for my Welcome and Todo components. Inside of Welcome, there is a Login component, which updates the AppContext to contain the user ID and jsonwebtoken.
import React, { useContext } from 'react'
import { BrowserRouter as Router, Route, Redirect } from 'react-router-dom'
import { Switch } from 'react-router'
import TodoApp from './components/TodoApp/TodoApp'
import Welcome from './components/Welcome/Welcome'
import TodoProvider from './components/TodoApp/TodoContext'
import { AppContext } from './AppContext'
export default function App () {
const context = useContext(AppContext)
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
context.loggedIn ? <Component {...props} /> : <Redirect to="/" />
}
/>
)
return (
<Router>
<Switch>
<Route exact path="/" component={Welcome} />
<TodoProvider>
<PrivateRoute exact path="/todo" component={TodoApp} />
</TodoProvider>
</Switch>
</Router>
)
}
Notice the context.loggedIn property it checks for, which you can see how it's changed in the last code block of this post.
Here is my Login component which is inside of a simple Welcome component
import React, { useState, useContext } from 'react'
import { AppContext } from '../../../AppContext'
export default function Login (props) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const context = useContext(AppContext)
const handleSubmit = async e => {
e.preventDefault()
try {
// Method which logs in using the /api/login route, and returns users ID and token to the global state (context)
await context.handleLogin(email, password)
} catch {
throw Error('Login error')
}
}
return (
<form handleSubmit={handleSubmit}>
<div className="input-wrapper">
<label htmlFor="email" />
<input
type="text"
id="login_email"
placeholder="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="input-wrapper">
<label htmlFor="password" />
<input
type="password"
id="login_password"
placeholder="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
</div>
<div className="input-wrapper">
<button
type="submit"
onClick={handleSubmit}
>
Log in
</button>
</div>
</form>
)
}
And finally, the method context.handleLogin which updates the state to contain the users ID and token. Taken from AppContext.js
handleLogin = (email, password) => {
axios
.post('http://localhost:4000/api/login/', {
email,
password
})
.then(response => {
this.setState({
loggedIn: response.httpStatus === 200,
userID: response.data.data.user._id,
token: response.data.data.token
})
})
.catch(err => {
this.setState({
httpStatus: err.response.status,
loginError: 'Incorrect email/password'
})
})
}
I'm new at React, so any help would be greatly appreciated. Thanks in advance!
I solved it by using withRouter() from react-router, and then pushing the history to the Private route.
Read more about it here:
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md