for some reason function, that is passed into a functional component never gets called.
The onSubmit function gets called but then it never calls login(email, password). Basically I pass the login function from auth.tsx into the Login component and then I try to call the function in onSubmit function inside Login component but the function never gets called. I tried adding console.log inside the login function but nothing gets returned in console nor I see any API requests in backend console.
Here is my code:
App.tsx
import React, { FunctionComponent } from 'react'
import Home from './pages/Home/Home'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { Activate, Login, Signup, ResetPassword, ResetPasswordConfirm } from './pages/Account'
import Layout from './components/Layout'
import { login } from "./actions/auth";
import { Provider } from 'react-redux'
import store from './store'
const App: FunctionComponent = () => (
<Provider store={store}>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login login={login}/>} />
<Route path="/signup" element={<Signup />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/password/reset/confirm/:uid/:token" element={<ResetPasswordConfirm />} />
<Route path="/activate/:uid/:token" element={<Activate />} />
</Routes>
</Router>
</Provider>
)
export default App
Login.tsx
import { FunctionComponent, useState } from "react";
import { Link, redirect } from "react-router-dom";
import { connect } from "react-redux";
interface IFormData {
email: string;
password: string;
}
interface ILogin {
login: (email: string, password: string) => void;
}
const Login: FunctionComponent<ILogin> = ({ login }) => {
const [formData, setFormData] = useState<IFormData>({
email: "",
password: "",
});
const { email, password } = formData;
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setFormData({
...formData,
[e.target.name]: e.target.value,
})
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
login(email, password)
}
return (
<div>
<h1>Sign In</h1>
<p>Sign into your account</p>
<form onSubmit={e => onSubmit(e)}>
<div>
<input
type="email"
placeholder="email"
name="email"
value={email}
onChange={e => onChange(e)}
required
/>
</div>
<div>
<input
type="password"
placeholder="password"
name="password"
value={password}
onChange={e => onChange(e)}
minLength={6}
required
/>
</div>
<button type="submit" >
Login
</button>
</form>
<p>
Don't have an account? <Link to="/signup">Sign Up</Link>
</p>
<p>
Forgot your password? <Link to="/reset-password">Reset Password</Link>
</p>
</div>
)
}
export default connect(null, { })(Login);
auth.tsx
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
USER_LOAD_SUCCESS,
USER_LOAD_FAIL
} from './types';
import axios from 'axios';
export const load_user = () => async (dispatch: any) => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/api/users/me/`, config);
dispatch({
type: USER_LOAD_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type: USER_LOAD_FAIL
});
}
} else {
dispatch({
type: USER_LOAD_FAIL
});
}
};
export const login = (username: string, password: string) => async (dispatch: any) => {
const config = {
headers: {
'Content-Type': 'application/json'
}
}
const body = JSON.stringify({ username, password });
console.log("yay")
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(load_user());
} catch (err) {
dispatch({
type: LOGIN_FAIL
});
}
}
This is because your login function is a curried function and hence if you wish to execute it directly you would need to do login(email, password)()
Your login function (defined in auth.tsx) seems to look like a thunk/saga in which case you should be dispatching the login function call from the Login component (defined in Login.tsx) using the MapDispatchToProps callback for the connect HOC.
Related
My LoginForm component requires an onSubmit property for to be passed in for tests so I can mock the onSubmit function. I've updated the components props, but I'm not sure what to pass in for the onSubmit property when I call the component from HashRouter. I tried passing an empty arrow function () => void, but Typescript won't allow empty functions. Any help would be appreciated.
I'm getting the following error from ESLint:
[eslint]
src/routes/routes.tsx
Syntax error: Identifier expected (11:6)
import React from "react";
import { HashRouter, Routes, Route } from "react-router-dom";
import LoginForm from "../components/login/LoginForm";
export default function Routes() {
return (
<HashRouter>
<Routes>
<Route path="/"></Route>
<Route path="/login" element={<LoginForm onSubmit={}/>}
</Routes>
</HashRouter>
);
}
import React from "react";
import { Field, Form, Formik } from "formik";
import { loginService } from "../../services/login/loginService";
import { yupValidationSchema } from "./yupValidationSchema";
export interface FormValues {
username: string;
password: string;
}
export interface FormProps {
onSubmit: (formValue: FormValues) => void;
}
const LoginForm = ({ onSubmit }: FormProps) => {
const [state, setState] = React.useState({
loginFailed: false,
});
const handleSubmit = async (values: FormValues) => {
const loginSuccess = await loginService(values.username, values.password);
if (loginSuccess) {
window.history.replaceState("#/login", "", "#/");
window.location.reload();
} else {
setState({
...state,
loginFailed: true,
});
}
onSubmit(values);
};
return (
<div>
<Formik
data-testid="form"
initialValues={{ username: "", password: "" }}
validationSchema={yupValidationSchema}
onSubmit={handleSubmit}
>
{({ errors, touched }) => (
<Form>
<Field data-testid={"username"} name="username" type="text" placeholder="Username" />
{touched.username && errors.username && <div>{errors.username}</div>}
<Field
data-testid={"password"}
name="password"
type="password"
placeholder="Password"
/>{" "}
<br />
{touched.password && errors.password && <div>{errors.password}</div>}
<button data-testid={"button"} name="submitButton" type="submit">
SUBMIT
</button>
{state.loginFailed && <div data-testid={"loginError"}>Unsuccessful Login</div>}
</Form>
)}
</Formik>
</div>
);
};
export default LoginForm;
I am a beginner and I am learning react js. I made a crud app for practice. I am implementing firebase to this app for authentication. But the problem is that I am not getting the updated value.
Index.jsx
Here in line no 11, I am not getting the latest user value.
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import ShowBooks from '../pages/ShowBooks';
import AddBook from '../pages/AddBook';
import EditBook from '../pages/EditBook';
import Login from '../pages/Login';
import Navbar from '../components/Navbar';
const Index = () => {
const { user } = useSelector(state => state.case);
let routes;
if (user === null || user === undefined) {
routes = (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/login" replace={true} />} />
</Routes>
);
} else {
routes = (
<>
<Navbar />
<Routes>
<Route path="/" element={<ShowBooks />} />
<Route path="/show-books" element={<ShowBooks />} />
<Route path="/add-book" element={<AddBook />} />
<Route path="/edit-book" element={<EditBook />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" replace={true} />} />
</Routes>
</>
);
}
return <BrowserRouter>{routes}</BrowserRouter>;
};
export default Index;
Here is the code of BooksSlice.jsx:
import { createSlice } from '#reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
const initialBooks = {
books: [
{
id: uuidv4(),
title: 'Ordinary Differential Equation',
author: 'Prof. Md. Abu Yousuf',
},
{ id: uuidv4(), title: 'CSE Kit', author: 'Gyan Bitan' },
],
user: JSON.parse(localStorage.getItem('user')) || null,
};
export const BooksSlice = createSlice({
name: 'books',
initialState: initialBooks,
reducers: {
showBooks: state => state,
addBook: (state, action) => {
state.books.push(action.payload);
},
deleteBook: (state, action) => {
const id = action.payload;
state.books = state.books.filter(book => book.id !== id);
},
updateBook: (state, action) => {
const { id, title, author } = action.payload;
const isBookExist = state.books.filter(book => book.id === id);
if (isBookExist) {
isBookExist[0].title = title;
isBookExist[0].author = author;
}
},
},
});
export const { showBooks, addBook, deleteBook, updateBook } =
BooksSlice.actions;
export default BooksSlice.reducer;
Here is the code of store.js
import { configureStore } from '#reduxjs/toolkit';
import booksReducer from './BooksSlice';
const store = configureStore({
reducer: {
case: booksReducer,
},
});
export default store;
And here is Login.jsx code:
import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase/firebase';
import { useNavigate } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(false);
const navigate = useNavigate();
// Login Handler Function
const loginHandler = event => {
event.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then(userCredential => {
// Signed in
const user = userCredential.user;
localStorage.setItem('user', JSON.stringify(user));
navigate('/show-books', { replace: true });
})
.catch(error => {
// const errorCode = error.code;
// const errorMessage = error.message;
setError(true);
});
};
return (
<div className="container pt-5">
{error && (
<p className="text-danger text-center">Wrong email or password!</p>
)}
<form className="w-75 mx-auto" onSubmit={loginHandler}>
<div className="form-floating mb-3">
<input
type="email"
className="form-control"
placeholder="Email"
value={email}
onChange={event => setEmail(event.target.value)}
required
/>
<label>Email</label>
</div>
<div className="form-floating my-3">
<input
type="password"
className="form-control"
placeholder="Password"
value={password}
onChange={event => setPassword(event.target.value)}
required
/>
<label>Password</label>
</div>
<button type="submit" className="btn btn-dark btn-lg text-light">
Log In
</button>
</form>
</div>
);
};
export default Login;
How can I get the updated user value on Index.jsx?
You need to create a "login" action and dispatch the authenticated user object to the store when a user authenticates.
Example:
export const BooksSlice = createSlice({
name: 'books',
initialState: initialBooks,
reducers: {
...
login: (state, action) => {
state.user = action.payload;
localStorage.setItem('user', JSON.stringify(action.payload));
},
},
});
export const {
showBooks,
addBook,
deleteBook,
updateBook,
login,
} = BooksSlice.actions;
export default BooksSlice.reducer;
...
import React, { useState } from 'react';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { useDispatch } from 'react-redux';
import { auth } from '../firebase/firebase';
import { useNavigate } from 'react-router-dom';
import { login } from '../path/to/BooksSlice';
const Login = () => {
...
const navigate = useNavigate();
const dispatch = useDispatch();
// Login Handler Function
const loginHandler = event => {
event.preventDefault();
signInWithEmailAndPassword(auth, email, password)
.then(userCredential => {
// Signed in
const user = userCredential.user;
dispatch(login(user)); // <-- dispatch user object to store
navigate('/show-books', { replace: true });
})
.catch(error => {
// const errorCode = error.code;
// const errorMessage = error.message;
setError(true);
});
};
return (
...
);
};
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.
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