I am new at React and i am trying to develop a login page. I did almost all but I could not re-render after successful login. I need to refresh the page to see the protected content. As far as I know, using custom Hook and changing its value should trigger a component re-render but although I change the value of it (token), page is not re-rendered. What is the point i missed?
//App.js
function App() {
const { token, setToken } = useToken();
if(!token) {
return <Login setToken={setToken} />
}
return (
<div className="wrapper">
<h1>Application</h1>
<BrowserRouter>
<Routes>
<Route path="/" element={<Dashboard />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
//UseToken.js
export default function useToken() {
const getToken = () => {
const tokenString = localStorage.getItem('token');
const userToken = JSON.parse(tokenString);
return userToken;
};
const [token, setToken] = useState(getToken());
const saveToken = userToken => {
localStorage.setItem('token', JSON.stringify(userToken));
setToken(userToken.token);
};
return {
setToken: saveToken,
token
}
}
//Login.js
async function loginUser(credentials) {
return fetch('http://localhost:8080/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
}).then(data => data.json())
.then(response => response.token)
}
export default function Login({ setToken }) {
const [username, setUserName] = useState();
const [password, setPassword] = useState();
const handleSubmit = async e => {
e.preventDefault();
const token = await loginUser({
username,
password
});
setToken(token);
}
return(
<div className="login-wrapper">
<h1>Please Log In</h1>
<form onSubmit={handleSubmit}>
<label>
<p>Username</p>
<input type="text" onChange={e => setUserName(e.target.value)}/>
</label>
<label>
<p>Password</p>
<input type="password" onChange={e => setPassword(e.target.value)}/>
</label>
<div>
<button type="submit">Submit</button>
</div>
</form>
</div>
)
}
Login.propTypes = {
setToken: PropTypes.func.isRequired
}
//Dashboard.js
export default function Dashboard() {
const logout = () => {
localStorage.clear();
}
return(
<div>
<h2>Dashboard</h2>
<button type="button" onClick={logout}>Logout</button>
</div>
);
}
In useToken.js
const saveToken = userToken => {
localStorage.setItem('token', JSON.stringify(userToken));
setToken(userToken.token);
}
should be replaced to
const saveToken = userToken => {
localStorage.setItem('token', JSON.stringify(userToken));
setToken(userToken); //userToken.token changed to userToken
}
Because, there is no variable named as 'token' in userToken object. It is token itself. So the hook thinks that there is no change and because of that it does not re-render.
Related
my teammate and I are stuck on solving a critical problem, which is how do we pass the user_id from one component to another in app.js . For example, we are able to register, login, and logout perfectly; but when we try to submit information in another component like personal form it says user_id is not defined. Also we are using JWT Tokens for authorization, and authentication. We are using local storage only, we did not implement redux.
App.js
import React, { Fragment, useState, useEffect } from "react";
import "./App.css";
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
} from "react-router-dom";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// import { useNavigation } from '#react-navigation/native';
import Home from "./components/Home";
import Login from "./components/Login";
import Register from "./components/Register";
import MedicalForm from "./components/MedicalForm";
import PersonalForm from "./components/PersonalForm";
import Navbar from "./components/Navbar/index";
toast.configure();
function App() {
// we want to make sure it set to false first
const [isAuthenticated, setAuthenticated] = useState(false);
//this is going to the be toggle function to set the auth
const setAuth = (Boolean) => {
setAuthenticated(Boolean);
};
// this is going to check if the user is authenticated even if the
// page is refreshed
async function isAuth() {
try {
const response = await fetch("http://localhost:4001/auth/is-verify", {
method: "GET",
headers: { token: localStorage.token },
});
const parseRes = await response.json();
parseRes === true ? setAuthenticated(true) : setAuthenticated(false);
console.log(parseRes);
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
isAuth();
});
return (
<Fragment>
<Router>
{/* reason why we use render instead of component props is because
anytime we send props to a component we don't want it to remount */}
<Navbar />
<div className="container">
<Routes>
{/* if(!isAuthenticated){ if this is true, pass setAuth to Login, and if it comes out true, then navigate to login page
<Login setAuth={setAuth} />}
else{
<Navigate to="/home" />
} */}
<Route
exact
path="/login"
element={
!isAuthenticated ? (
<Login setAuth={setAuth} />
) : (
<Navigate to="/home" />
)
}
/>
<Route
exact
path="/register"
element={
!isAuthenticated ? (
<Register setAuth={setAuth} />
) : (
<Navigate to="/login" />
)
}
/>
<Route
exact
path="/home"
element={
isAuthenticated ? (
<Home setAuth={setAuth} />
) : (
<Navigate to="/login" />
)
}
/>
<Route
exact
path="/mform"
element={
isAuthenticated ? (
<MedicalForm setAuth={setAuth} />
) : (
<Navigate to="/login" />
)
}
/>
<Route
exact
path="/pform"
element={
isAuthenticated ? (
<PersonalForm setAuth={setAuth} />
) : (
<Navigate to="/login" />
)
}
/>
</Routes>
</div>
</Router>
</Fragment>
);
}
export default App;
PersonalForm.js
`Login.js
import React, { Fragment, useState } from "react";
// import { Link } from "react-router-dom";
import { toast } from "react-toastify";
const Personalform = (props) => {
const [username, setUsername] = useState("");
const [inputs, setInputs] = useState({
first_name: "",
last_name: "",
pronoun: "",
occupation: "",
phone_number: "",
city: "",
state: "",
zip: "",
});
const {
first_name,
last_name,
pronoun,
occupation,
phone_number,
city,
state,
zip,
} = inputs;
const onChange = (e) => {
// take in every input and target the input value of name
//like email,username, and password
setInputs({ ...inputs, [e.target.name]: e.target.value });
};
const onSubmitForm = async (e) => {
e.preventDefault();
try {
const body = {
first_name,
last_name,
pronoun,
occupation,
phone_number,
city,
state,
zip,
};
// console.log(user_id)
const response = await fetch(
`http://localhost:4001/pform/${props.user_id}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
token: localStorage.token,
},
body: JSON.stringify(body),
}
);
const parseRes = await response.json();
setUsername(parseRes.username);
if (parseRes.token) {
// we want to save the token to our local storage
localStorage.setItem("token", parseRes.token);
console.log(parseRes);
//now we want to setAuth to true
props.setAuth(true);
toast.success("submit succesfully"); // then use toastify
} else {
// if false
props.setAuth(false); // set auth to false
toast.error(parseRes); // set the toast to send and error
}
} catch (err) {
console.error(err.message);
}
};
const logout = (e) => {
e.preventDefault();
localStorage.removeItem("token");
props.setAuth(false);
toast.success("Logged out successfully");
};
return (
<Fragment>
{username}
<h1 className="text-center my-5">Personal Form</h1>
<form onSubmit={onSubmitForm}>
<input
type="text"
// this is a name of an input
name="first_name"
placeholder="first_name"
className="form-control my-3"
value={first_name}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="last_name"
placeholder="Last Name"
className="form-control my-3"
value={last_name}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="pronoun"
placeholder="pronoun"
className="form-control my-3"
value={pronoun}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="occupation"
placeholder="occupation"
className="form-control my-3"
value={occupation}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="phone_number"
placeholder="phone number"
className="form-control my-3"
value={phone_number}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="city"
placeholder="city"
className="form-control my-3"
value={city}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="state"
placeholder="state"
className="form-control my-3"
value={state}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="zip"
placeholder="zip"
className="form-control my-3"
value={zip}
onChange={(e) => onChange(e)}
/>
<button className="btn btn-success btn-block">Submit</button>
</form>
<button className="btn btn-primary" onClick={(e) => logout(e)}>
logout
</button>
</Fragment>
);
};
export default Personalform;
index.js or the navbar component
import React from "react";
import {
Nav,
NavLink,
Bars,
NavMenu,
NavBtn,
NavBtnLink,
} from "./NavbarElements";
const Navbar = () => {
return (
<>
<Nav>
<NavLink to="/">
<h1>Logo</h1>
</NavLink>
<Bars />
<NavMenu>
<NavLink to="/pform" activeStyle>
Personal Form
</NavLink>
</NavMenu>
<NavBtn>
<NavBtnLink to="/login">Login</NavBtnLink>
</NavBtn>
</Nav>
</>
);
};
export default Navbar;
login.js
import React, { Fragment, useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "react-toastify";
const Login = ({ setAuth }) => {
const [inputs, setInputs] = useState({
email: "",
password: "",
});
const { email, password } = inputs;
const onChange = (e) => {
setInputs({ ...inputs, [e.target.name]: e.target.value });
};
const onSubmitForm = async (e) => {
e.preventDefault();
try {
const body = { email, password };
const response = await fetch("http://localhost:4001/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const parseRes = await response.json();
if (parseRes.token) {
localStorage.setItem("token", parseRes.token);
setAuth(true);
toast.success("login successfully!");
} else {
setAuth(false);
toast.error(parseRes);
}
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<h1>Login</h1>
<form onSubmit={onSubmitForm}>
<input
type="email"
name="email"
placeholder="email"
className="form-control my-3"
value={email}
onChange={(e) => onChange(e)}
/>
<input
type="password"
name="password"
placeholder="password"
className="form-control my-3"
value={password}
onChange={(e) => onChange(e)}
/>
<button className="btn btn-success btn-block">submit</button>
</form>
<Link to="/register">Register</Link>
</Fragment>
);
};
export default Login;
Register
import React, { Fragment, useState } from "react";
import { Link } from "react-router-dom";
import { ToastContainer, toast } from "react-toastify";
const Register = ({ setAuth }) => {
const [inputs, setInputs] = useState({
email: "",
password: "",
username: "",
});
const { email, password, username } = inputs;
const onChange = (e) => {
// take in every input and target the input value of name
//like email,username, and password
setInputs({ ...inputs, [e.target.name]: e.target.value });
};
const onSubmitForm = async (e) => {
e.preventDefault();
try {
const body = { email, password, username };
const response = await fetch("http://localhost:4001/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const parseRes = await response.json();
if (parseRes.token) {
localStorage.setItem("token", parseRes.token);
setAuth(true);
toast.success("Registered Successfully!");
} else {
setAuth(false);
toast.error(parseRes);
}
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<h1 className="text-center my-5">Register</h1>
<form onSubmit={onSubmitForm}>
<input
type="email"
// this is a name of an input
name="email"
placeholder="email"
className="form-control my-3"
value={email}
onChange={(e) => onChange(e)}
/>
<input
type="password"
name="password"
placeholder="password"
className="form-control my-3"
value={password}
onChange={(e) => onChange(e)}
/>
<input
type="text"
name="username"
placeholder="username"
className="form-control my-3"
value={username}
onChange={(e) => onChange(e)}
/>
<button className="btn btn-success btn-block">Submit</button>
</form>
<Link to="/login">Login</Link>
</Fragment>
);
};
export default Register;
jwtauth.js
const router = require("express").Router();
const { json, response } = require("express");
const pool = require("../db");
const bcrypt = require("bcrypt");
const jwtGenerator = require("../utils/jwtGenerator");
const validInfo = require("../middleware/validInfo");
const authorization = require("../middleware/authorization");
//registering
router.post("/register", validInfo, async (req, res) => {
try {
// 1. destructure the req.body(name,email,password)
const { username, email, password } = req.body;
// 2. check if user exists (if user exists then throw error)
const user = await pool.query(
"SELECT * FROM login_credentials WHERE email =$1",
[email]
);
if (user.rows.length !== 0) {
return res.status(401).json("User already exists");
}
// res.json(user.rows);
// 3. bycrpyt the user password
const saltRound = 10;
const salt = await bcrypt.genSalt(saltRound);
const bcryptPassword = await bcrypt.hash(password, salt);
// 4. enter the new user inside our database
const newUser = await pool.query(
"INSERT INTO login_credentials (username,email,password) VALUES ($1,$2,$3) RETURNING *",
[username, email, bcryptPassword]
);
// res.json(newUser.rows[0]);
// 5. generate our jwt token
const token = jwtGenerator(newUser.rows[0].user_id);
res.json({ token });
} catch (err) {
console.error(err.message);
res.status(500).send("server error");
}
});
//login route
router.post("/login", validInfo, async (req, res) => {
try {
//1. destructure the req.body
const { email, password } = req.body;
//2. check if user doesn't exist (if not we throw error)
const user = await pool.query(
"SELECT * FROM login_credentials WHERE email=$1",
[email]
);
if (user.rows.length === 0) {
return res.status(401).json("password or email is incorrect");
}
//3. check if incoming password is the same the database password
const validPassword = await bcrypt.compare(password, user.rows[0].password);
console.log(validPassword);
if (!validPassword) {
return res.status(401).json("password or email is incorrect");
}
//4. give the jwt token
const token = jwtGenerator(user.rows[0].user_id);
res.json({ token });
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
router.get("/is-verify", authorization, async (req, res) => {
try {
res.json(true);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
module.exports = router;
authorization.js
const jwt = require("jsonwebtoken");
require("dotenv").config();
// before it hits routes it's going to get access to the requested
// resonse then if everything ends up working ok, it will continue on
// with the process of next so it can keep going with the routes
module.exports = async (req, res, next) => {
try {
const jwtToken = req.header("token");
if (!jwtToken) {
return res.status(401).json("Not Authorized");
}
// if this is verified it is going to return us a payload that we can use within our routes
const payload = jwt.verify(jwtToken, process.env.jwtSecret);
req.user = payload.user;
next();
} catch (err) {
console.error(err.message);
return res.status(403).json("Not Authorized");
}
};
validInfo.js
module.exports = (req, res, next) => {
const { email, username, password } = req.body;
function validEmail(userEmail) {
return /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(userEmail);
}
if (req.path === "/register") {
if (![email, username, password].every(Boolean)) {
return res.status(401).json("Missing Credentials");
} else if (!validEmail(email)) {
return res.status(401).json("Invalid Email");
}
} else if (req.path === "/login") {
if (![email, password].every(Boolean)) {
return res.status(401).json("Missing Credentials");
} else if (!validEmail(email)) {
return res.status(401).json("Invalid Email");
}
}
next();
};
you can create a file for auth context then create a context and export it
export const AuthContext= React.createContext({userId:"",setUserId: ()=>{}}); //the param here should be the default value (default shape of the object you wanna pass to children)
Then in your app.js import that context and wrap your other components with
const [userId, setUserId] = useState("");
<AuthContext.Provider value={{userId:userId, setUserId:setUserId}}> //the object contains the state or functions you wanna access from child components
//your other components
</AuthContext.Provider>
now inside any screen or component that needs to access or set the user id (or any other value or callback you passed) you can just import the context and access the value like this
const {userId,setUserId} = useContext(AuthContext)
you can also refer to this example:
https://fatmali.medium.com/use-context-and-custom-hooks-to-share-user-state-across-your-react-app-ad7476baaf32
You can set vars in res object like this in node.js. But it your code is react not node.js.
res.user.user_id = user_id;
and then use it anywhere with:
let user_id = res.user.user_id;
Private route /profile rendering me an error.
My app works until I try to go on /profile.
I have never seen this error yet, so while Im searching Im posting here, maybe someone has an idea.
Also Im looking for a good documentation/tutorial on how to make private routes using jwt and js-cookie.
error
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
App.js
import { useState } from 'react';
import Axios from "axios";
import Cookies from "js-cookie";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import ProtectedRoute from './components/ProtectedRoute';
import Profile from "./pages/Profile";
const App = () => {
const [emailRegistration, setEmailRegistration] = useState("");
const [passwordRegistration, setPasswordRegistration] = useState("");
const [emailLogin, setEmailLogin] = useState("");
const [passwordLogin, setPasswordLogin] = useState("");
const [isAuth, setIsAuth] = useState(false);
const register = () => {
Axios.post('http://localhost:3000/api/signup', {
user:{
email: emailRegistration,
password: passwordRegistration
}
}).then((response) => {
console.log(response);
});
};
const login = () => {
fetch("http://localhost:3000/api/login", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user: {
email: emailLogin,
password: passwordLogin,
},
}),
})
.then((res) => {
if (res.ok) {
console.log(res.headers.get("Authorization"));
localStorage.setItem("token", res.headers.get("Authorization"));
setIsAuth(true)
return res.json();
} else {
return res.text().then((text) => Promise.reject(text));
}
})
.then((json) => console.dir(json))
.catch((err) => console.error(err));
};
const getArticles = () => {
fetch("http://localhost:3000/articles", {
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (res.ok) {
return res.json();
} else if (res.status == "401") {
throw new Error("Unauthorized Request. Must be signed in.");
}
})
.then((json) => console.dir(json))
.catch((err) => console.error(err));
}
const logout = () => {
fetch("http://localhost:3000/api/logout", {
method: "delete",
headers: {
"Content-Type": "application/json",
Authorization: localStorage.getItem("token"),
},
})
.then((res) => {
if (res.ok) {
setIsAuth(false)
return res.json();
} else {
return res.json().then((json) => Promise.reject(json));
}
})
.then((json) => {
console.dir(json);
})
.catch((err) => console.error(err));
};
const testBearer = () => {
let cookie = Cookies.get('token');
console.log(cookie)
}
return (
<Router>
<div className="app">
{isAuth ?
(
<button onClick={logout} > logout </button>
)
:
(
<div className="auth">
<div className="registration">
<h1> Registration </h1>
<label> Email </label>
<input
type="email"
onChange={(e) => {
setEmailRegistration(e.target.value);
}}
/>
<label> Password </label>
<input
type="password"
onChange={(e) => {
setPasswordRegistration(e.target.value);
}}
/>
<button onClick={register} > Register </button>
</div>
<div className="login">
<h1> Login </h1>
<label> Email </label>
<input
type="email"
onChange={(e) => {
setEmailLogin(e.target.value);
}}
/>
<label> Password </label>
<input
type="password"
onChange={(e) => {
setPasswordLogin(e.target.value);
}}
/>
<button onClick={login} > Login </button>
</div>
</div>
)
}
<br />
<hr />
<br />
<button onClick={getArticles} >getArticles </button>
<button onClick={testBearer} >testCookie </button>
<button onClick={logout} > logout </button>
</div>
<Route path="/">
</Route>
<ProtectedRoute exact path="/profile" component={Profile} isAuth={isAuth} />
</Router>
)};
export default App;
Profile.js
import React from 'react';
import {withRouter} from "react-router-dom";
const Profile = () => {
return (
<div>
if you see this you entered the auth.
</div>
);
};
export default withRouter(Profile);
ProtectedRoute.js
import React from 'react';
import {Route, Redirect} from "react-router-dom";
const ProtectedRoute = ({isAuth: isAuth, component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => {
if (isAuth) {
return <Component />
} else {
return (
<Redirect to={{pathname: "/profile", state: {from: props.location }}} />
);
}
}}/>
);
};
export default ProtectedRoute;
Let your ProtectedRoutes to have a type based on their auth capabilities such as LoginRoute and PrivateRoute. Your Profile route acts as the login page. Therefore, it doesn't need to be user authenticated to view that page. Then create another route as dashboard, which will be the page user able to view in a successful log in. Now, keep your routes as follows.
<Route path="/"></Route>
<ProtectedRoute
exact
path="/profile"
component={Profile}
isAuth={isAuth}
type="LoginRoute"
/>
<ProtectedRoute
exact
path="/dashboard"
component={() => <div>User Entered into Dashboard</div>}
isAuth={isAuth}
type="PrivateRoute"
/>
Then, you need to handle the type based authentication functionality inside your ProtectedRoute component as follows.
import React from "react";
import { Route, Redirect } from "react-router-dom";
const getRouteVariables = (type, isAuth) => {
switch (type) {
case "LoginRoute":
return [!isAuth, "/dashboard"];
case "PrivateRoute":
return [isAuth, "/profile"];
default:
return [isAuth, "/profile"];
}
};
const ProtectedRoute = ({ type, isAuth, component: Component, ...rest }) => {
const [isRouteChanged, redirectPath] = getRouteVariables(type, isAuth);
return (
<Route
{...rest}
render={(props) => {
if (isRouteChanged) {
return <Component />;
} else {
return (
<Redirect
to={{ pathname: redirectPath, state: { from: props.location } }}
/>
);
}
}}
/>
);
};
export default ProtectedRoute;
getRouteVariables function return isRouteChanged and redirectPath. isRouteChanged means whether route needs to be changed based on isAuth and redirectPath means which path needs to be taken if redirection happens.
For LoginRoute type /profile route only comes up when isAuth becomes false. If isAuth is true, then moving to /profile route will redirect to /dashboard.
For PrivateRoute type /dashboard route only possible to access when isAuth becomes true. If isAuth is false, then moving to /dashboard route will redirect to /profile.
My app calls a Login api and returns me a token,i stored the token in localStorage so my problem is how i validade if the user has a token to do the login. what can i do to do that?
this is my login Page where i used to add the token to the localStorage
import React, { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { login } from '../services/login.services';
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const history = useHistory();
const loginHandler = async () => {
const result = await login(email, password);
console.log(result);
localStorage.setItem('token', result?.response?.result?.token);
localStorage.getItem('token');
};
return (
<section className='layout'>
<div className='wrp-login'>
<div className='container'>
<h1 color='white'>Login Page</h1>
<div className='col-sm-6 offset-sm-3'>
<input
type='text'
placeholder='email'
onChange={(e) => setEmail(e.target.value)}
className='input-wrapper'
/>
<br />
<input
type='password'
placeholder='password'
onChange={(e) => setPassword(e.target.value)}
className='input-wrapper'
/>
<br />
<button onClick={() => loginHandler()} className='button'>
Login
</button>
</div>
</div>
</div>
</section>
);}export default Login;
this is my Login Service wher i do the api call and return data
export const login = async (email, password) => {
try {
const result = await fetch(
'teste.com',
{
method: 'Post',
body: JSON.stringify({ login: email, senha: password }),
headers: {
'Content-Type': 'application/json',
},
mode: 'cors',
cache: 'default',
},
);
return await result.json();
} catch (err) {
return err;
}};
Someone could help me?
You can validate and redirect users by creating a wrapper for protected routes with react-router-dom to the login page if they don't have the token stored this way:
const ProtectedRoute = (props) => {
const token = localStorage.getItem('token');
if (token == null) {
return <Redirect to={Routes.LOGIN} />;
}
return <>{props.children}</>;
};
I have a functional component that sets state with an onSubmit function. The problem is that the setState function won't update the user state in time for it to be passed down to the user prop before the authenticated state is changed to true. Here is the basic gist of my component:
import React, { useState } from 'react';
import axios from 'axios';
import { LoginForm } from './LoginForm';
import { LoggedIn } from './LoggedIn';
const { login } = require('../utils/login');
const { getUser } = require('../utils/getUser');
export const Splash = () => {
const [user, setUser] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const fetchUser = await getUser(token);
setAuthenticated(true);
setUser(fetchUser);
console.log(fetchUser) <---- logs user fine
console.log(user) <----- logs empty object
};
const _handleLogout = async (e) => {
const logout = await
axios.get('http://localhost:5000/api/v1/auth/logout');
console.log(
`Logout status: ${logout.status}, Success: ${logout.data.success}`
);
setAuthenticated(false);
setUser({});
};
return (
<div>
{!authenticated ? (
<LoginForm
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
/>
) : (
<LoggedIn
user={user}
authenticated={authenticated}
logout={_handleLogout}
/>
)}
</div>
);
};
For whatever reason, my setAuthenticated function will change the authenticated state from false to true, hence triggering the render of <LoggedIn />, but my user state will not update in time to be passed down to <LoggedIn /> through the user prop. I suspect the authenticated prop I send down isn't updated either. Thoughts?
Thanks!
EDIT: Here's my <LoggedIn /> component and it's <LoggedInNav />component.
/// LoggedIn.js
export const LoggedIn = ({ logout, user, authenticated }) => {
LoggedIn.propTypes = {
logout: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
authenticated: PropTypes.bool.isRequired
};
const [loggedInUser, setUser] = useState({});
const [loggedInAuth, setAuthenticated] = useState(false);
useEffect(() => {
try {
if (user) {
setAuthenticated(true);
setUser(user);
}
} catch (err) {
console.log(err);
}
}, []);
return (
<div>
{!authenticated ? (
<Spinner animation='border' variant='primary' />
) : (
<LoggedinNav navUser={user} logoutButton={logout} />
)}
</div>
);
};
// LoggedInNav.js
export const LoggedinNav = ({ navUser, logoutButton }) => {
LoggedinNav.propTypes = {
navUser: PropTypes.object.isRequired,
logoutButton: PropTypes.func.isRequired
};
return (
<div>
<Navbar bg='light' variant='light'>
<Navbar.Brand href='#home'>
<img
src={logo}
width='120'
height='auto'
className='d-inline-block align-top ml-3'
alt='React Bootstrap logo'
/>
</Navbar.Brand>
<Nav className='mr-auto'>
<Nav.Link href='#feedback'>Submit Feedback</Nav.Link>
</Nav>
<Navbar.Collapse className='justify-content-end mr-4'>
<Navbar.Text>
Signed in as: {navUser.name.first} {navUser.name.last} - Role:{' '}
{navUser.role}
</Navbar.Text>
</Navbar.Collapse>
<Button onClick={logoutButton} variant='outline-primary mr-4'>
Logout
</Button>
</Navbar>
</div>
);
};
According to React Docs, useState, same as setState, triggers a new render.
Next render, the new value will be in user.
Because you are in an async method, youre out of the loop, and cannot see new state value in the next line.
See codesandbox poc:
In it the async function awaits 2 seconds and then set state.
You can see in the console that the render is triggered after the set state, the render sees the new state, but the async method prints the old one.
useState, same as setState, are syncronous, meaning they will directly trigger a render.
For async work, you can use useEffect
me and my business partner is coming up with a basic coding model for our respective fields. He's backend, while I'll be doing front end. He made a test api with endpoints that I'm trying to connect to via React/Axios/Formik. I tested to make sure that the endpoints work in postman, and they do, so now I just have to properly code it on my end. Yet I seem to be having trouble. I keep getting a "NET::ERR_SSL_Protocol_Error" when ever I try to make a post via a login/register component I built. Here is the code for each relevant component:
Update:
Thanks to a user here, I managed to get rid of that pesky SSL error. Now I'm getting a 400 status. I'm not sure how to pass in data to a api via formik. Here's the updated code for registration below:
SignUp.js
// import React, {useState} from "react";
// import axiosWithAuth from "../utilils/axiosWithAuth";
// import { useHistory } from "react-router-dom";
// const initialState = {
// username: "",
// password: "",
// email: "",
// role: ""
// };
// // const initialFormErrors = {
// // username: "",
// // password: "",
// // };
// const Registration = (props) => {
// const { push } = useHistory();
// const [formValues, setFormValues] = useState(initialState);
// // const [formErrors, setFormErrors] = useState(initialFormErrors);
// // const [disabled, setDisabled] = useState(togDisabled);
// const handleChange = e => {
// const name = e.target.name;
// const value = e.target.value;
// setState({
// ...state.credentials,
// [name]:value
// });
// setFormValues({
// ...formValues,
// [name]:value
// });
// };
// const register = e => {
// e.preventDefault();
// const signUpData = {
// username: formValues.name,
// password: formValues.password,
// email: formValues.email,
// role: formValues.role
// }
// axiosWithAuth()
// .post("/api/auth/register", signUpData)
// .then((res) => {
// console.log(res);
// push("/login");
// })
// .catch(err =>
// console.error("bk: SignUp.js: Registration: err.message: ", err.message)
// );
// };
// return (
// <div>
// <form onSubmit={register}>
// <div>
// <label>Username</label>
// <input
// type="text"
// name="username"
// value={formValues.name}
// onChange={handleChange}
// /> </div>
// <div>
// <label>Password</label>
// <input
// type="password"
// name="password"
// value={formValues.password}
// onChange={handleChange}
// /></div>
// <div>
// <label>Email</label>
// <input
// type="email"
// name="email"
// value={formValues.email}
// onChange={handleChange}
// /></div>
// <div>
// <label>Role</label>
// <input
// type="text"
// name="role"
// value={formValues.role}
// onChange={handleChange}
// /></div>
// <button>Sign Up</button>
// </form>
// </div>
// );
// }
// export default Registration;
import React, { useState } from 'react';
import axiosWithAuth from '../utilils/axiosWithAuth';
import { useHistory } from 'react-router-dom';
import {Formik, Form} from 'formik';
import FormikControl from '../Forms/FormikControl';
const Registration = (props) => {
const { push } = useHistory();
const initialValues = {
username: '',
password: '',
email: '',
role:''
}
const [state, setState] = useState(initialValues);
const validate = values => {
let errors = {}
//If each of the values are not valid return a required text, otherwise return null.
if (!values.username) { errors.username = 'required' };
if (!values.password) { errors.password = 'required' };
if (!values.email) { errors.email = 'required' };
if (!values.role) { errors.role = 'required' };
return errors;
}
const onSubmit = (values) => {
console.log('form data', values);
axiosWithAuth()
.post("/api/auth/register", state )
.then(res => {
setState(res.values)
push("/login");
})
.catch(err =>
console.error("bk: Login.js: Register: err.message: ", err.message)
);
}
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={onSubmit}
>
{formik => (
<Form>
<FormikControl
control='input'
type='text'
label='Username'
name='username'
// value={initialValues.username}
/>
<FormikControl
control='input'
type='password'
label='Password'
name='password'
// value={initialValues.password}
/>
<FormikControl
control='input'
type='email'
label='Email'
name='email'
// value={initialValues.email}
/>
<FormikControl
control='input'
type='text'
label='Role'
name='role'
// value={initialValues.role}
/>
<button type='submit'>Submit</button>
</Form>
)}
</Formik>
)
}
export default Registration;
Login.js
import React from 'react';
import axiosWithAuth from '../utilils/axiosWithAuth';
import setToken from '../utilils/';
import { useHistory } from 'react-router-dom';
import {Formik, Form} from 'formik';
import FormikControl from '../Forms/FormikControl';
const Login = (props) => {
const { push } = useHistory();
const initialValues = {
username: '',
password: '',
}
const validate = values => {
let errors = {}
//If each of the values are not valid return a required text, otherwise return null.
if (!values.username) { errors.username = 'required' };
if (!values.password) { errors.password = 'required' };
return errors;
}
const handleSubmit = (e) => {
const loginData = {
username: initialValues.username ,
password: initialValues.password
}
axiosWithAuth()
.post("/api/auth/login", loginData)
.then(res => {
setToken(res.data.token);
push("/protected");
})
.catch(err =>
console.error("bk: Login.js: login: err.message: ", err.message)
);
}
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{formik => (
<Form>
<FormikControl
control='input'
type='text'
label='Username'
name='username'
/>
<FormikControl
control='input'
type='password'
label='Password'
name='password'
/>
<button type='submit'>Submit</button>
</Form>
)}
</Formik>
)
}
export default Login;
axiosWithAuth.js
import axios from "axios";
const axiosWithAuth = () => {
const token = localStorage.getItem("token");
return axios.create({
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
baseURL: "http://localhost:8000"
});
};
export default axiosWithAuth;
tokenStore.js
const tokenState = "";
//
export const getToken = () => {
return localStorage.getItem(tokenState);
};
//
export const setToken = (newToken) => {
localStorage.setItem(tokenState, newToken);
};
//
export const clearToken = () => {
localStorage.removeItem(tokenState);
};
PrivateRoute.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { getToken } from './tokenStore';
const PrivateRoute = ({ component: Component, ...props }) => {
const token = getToken();
return (
<Route
{...props}
render = {() => {
return token ? <Component />: <Redirect to="/login" />;
}}
/>
);
};
export default PrivateRoute;
App.js
import React from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import Login from './Components/Login';
import PrivateRoute from './utilils/PrivateRoute';
import Registration from './Components/SignUp';
import DevPage from './Pages/Dev';
function App() {
return (
<Router >
<div>
<nav>
<Link to="/login">Login </Link>
<Link to="/SignUP">SignUp </Link>
<Link to="/protected"> Dev</Link>
</nav>
<Switch>
<PrivateRoute
path="/protected"
component={DevPage} />
<Route exact path="/login" component={Login} />
<Route exact path="/SignUP"><Registration/></Route>
<Route component={Login} />
</Switch>
</div>
</Router>
);
}
export default App;
I plan on refactoring the code in ContextAPI once the tests turn out successful. All I'm asking for here is validation that the code is proper and to fill in the blanks of what I'm missing or what I need to correct. Let me know if you need the backend code as well and I'll add that in future edits. Thank you in Advance.
You're calling httpS://localhost:8000 in axiosWithAuth.js, the error is for an invalid or inexistent SSL certificate. Call http://localhost:8000 while you're in development and make sure to transition to https when you know where you're hosting this thing and have a valid url and ssl certificate