I am building a full-stack React App with mongodb and I am trying to persist user data when I log in using localStorage. However, the localStorage is only storing the null value. I have failed to figure out what I am doing wrong, a little would be golden at this point. I am using contextApi for this, and I have provided the code below.... Please save me, your help will be heavily appreciated. The login route works perfectly fine in the backend.
I expected the localstorage to save the user information when I login. The login in works, and as a result, the login proceeds to the home page as expected ,but the user information does not persist in the localstorage but rather returns a null value.
Here is the login page code
import React, { useState, useContext } from 'react'
import { AuthContext } from '../../context/AuthContext';
import axios from "axios";
import { useNavigate } from "react-router-dom";
import "./login.css";
const Login = () => {
const [credentials, setCredentials] = useState({
username: undefined,
password: undefined
});
const { user ,loading, error, dispatch } = useContext(AuthContext);
const navigate = useNavigate();
const handleChange = (e) => {
setCredentials((prev) => ({ ...prev, [e.target.id]: e.target.value}));
}
const handleClick = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/auth/login", credentials);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data })
navigate("/")
} catch(err) {
dispatch({type: "LOGIN_FAILURE", payload: err.response.data})
}
}
console.log(user);
return (
<div className='login'>
<div className="lContainer">
<input type="text" placeholder='username' id='username' onChange={handleChange} className="lInput" />
<input type="password" placeholder='password' id='password' onChange={handleChange} className="lInput" />
<button disabled={loading} onClick={handleClick} className="lButton">Login</button>
{error && <span>{error.message}</span>}
</div>
</div>
)
}
export default Login;
This is the contextApi code for the user-authentication
import { createContext, useReducer, useEffect } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null
};
case "LOGIN_SUCCCES":
return {
user: action.payload,
loading: false,
error: null
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null
}
default:
return state
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
/**
* ! save user to localstorage
*/
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch
}}
>
{children}
</AuthContext.Provider>
)
}
I expected the localstorage to save the user information when I login. The login in works, and as a result, the login proceeds to the home page as expected ,but the user information does not persist in the localstorage but rather returns a null value.
Related
I am making an API call using Axios and after that I am send those details to context API but I am getting null. I am using formik to send data to backend and on submit of that form I make an api call using axios then get the user from backend end pass it on to context API.
UserContext
import { createContext, useReducer } from "react";
import UserReducer from "./UserReducer";
const INITIAL_STATE = {
user: null,
};
export const UserContext = createContext(INITIAL_STATE);
export const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(UserReducer, INITIAL_STATE);
const setUser = (userDetails) => {
dispatch({
type: "GET_USER",
payload: userDetails,
});
// Here it is returning the user data but INITIAL_STATE.user is null
};
return (
<UserContext.Provider
value={{
user: state.user,
setUser,
}}
>
{children}
</UserContext.Provider>
);
};
UserReducer
const UserReducer = (state, action) => {
switch (action.type) {
case "GET_USER":
return {
user: action.payload,
};
default:
return state;
}
};
export default UserReducer;
Login
const { user, setUser } = useContext(UserContext);
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
onSubmit: () => {
const getUser = async () => {
const userData = await Axios.post("http://localhost:3001/login", {
email: formik.values.email,
password: formik.values.password,
});
setUser(userData.data); // Here I am sending the data to context API
};
getUser();
},
validationSchema,
});
I'm trying to display alert if the user submit bad email and password, but I'm struggling with this.
the errors and load states are updated when dispatch (login(user)) is executed.
but When the user click on login to submit the form the errors and loading aren't checked and directly navigate to the home page ('/').
this is my login screen code :
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Alert, Button, Form, Spinner } from "react-bootstrap";
import { useNavigate } from "react-router-dom";
import { login } from "../JS/Action/user";
const Login = () => {
const [User, setUser] = useState({});
const errors = useSelector((state) => state.userReducer.errors);
const loading = useSelector((state) => state.userReducer.loadUser);
const dispatch = useDispatch();
const navigate = useNavigate();
const handleChnage = (e) => {
setUser({ ...User, [e.target.name]: e.target.value });
};
const handleUser = (e) => {
e.preventDefault();
dispatch(login(User))
navigate('/')
};
return (
<div
>
{errors && errors.map(el=><Alert variant="danger">{el.msg}</Alert>)}
{loading && <Spinner animation="border" variant="secondary" />}
<Form >
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" name="email" onChange={handleChnage} />
<Form.Label>Password</Form.Label>
<Form.Control type="text" placeholder="Password" name="password" onChange={handleChnage} />
<Button variant="primary" type="submit" disabled={loading} onClick={handleUser} >
login
</Button>
</Form>
</div>
);
};
export default Login;
this is the login Action :
//LOGIN USER
export const login = (user) => async (dispatch) => {
dispatch({ type: LOAD_USER });
try {
let result = await axios.post("/api/user/login", user);
dispatch({ type: LOGIN_USER, payload: result.data });
} catch (error) {
dispatch({ type: FAIL_USER, payload: error.response.data.errors });
}
};
and this is the user reducer :
//import
import {
CLEAR_ERRORS,
CURRENT_USER,
FAIL_USER,
LOAD_USER,
LOGIN_USER,
LOGOUT_USER,
REGISTER_USER,
} from "../ActionTypes/user";
//initial state
const initialState = {
user: null,
loadUser: false,
errors: null,
isAuth: false,
isAdmin: false
};
//pure function
const userReducer = (state = initialState, { type, payload }) => {
switch (type) {
case LOAD_USER:
return { ...state, loadUser: true };
case REGISTER_USER:
localStorage.setItem("token", payload.token);
return {
...state,
user: payload.user,
loadUser: false,
isAuth: true,
isAdmin: payload.user.isAdmin,
};
case LOGIN_USER:
localStorage.setItem("token", payload.token);
return {
...state,
loadUser: false,
isAuth: true,
user: payload.user,
isAdmin: payload.user.isAdmin,
};
case CURRENT_USER:
return {
...state,
loadUser: false,
isAuth: true,
user: payload,
isAdmin: payload.isAdmin,
};
case LOGOUT_USER:
localStorage.removeItem("token");
return {
user: null,
loadUser: false,
errors: null,
isAuth: false,
isAdmin: false
};
case FAIL_USER:
return { ...state, errors:payload, loadUser: false };
case CLEAR_ERRORS:
return { ...state, errors:null };
default:
return state;
}
};
//export
export default userReducer;
sorry for my bad englih guys hope you got the idea and you can help me..
You don't have to use navigate("/") in the handleUser function as it will not check whether the user is logged in or not but will navigate.
const handleUser = (e) => {
e.preventDefault();
dispatch(login(User))
// navigate('/')
};
Instead, use useEffect hook to check whether the user is logged in or not and alert and navigate.
const user = useSelector((state) => state.userReducer.user);
const error = useSelector((state) => state.userReducer.errors);
useEffect(()=>{
if(user){
alert("Logged in successfully")
navigate("/")
}
if(error){
alert(error)
}
},[navigate,user,error])
Hope it helps, Thanks.
I'm trying to set up a simple authentication system using react's context api. I have two react pages here using react router, Login.js and App.js.
Here's App.js, I want it to use the isAuthenticated boolean from the context api to decide which page to render:
App.js:
function App() {
const { isAuthenticated } = useContext(AuthContext);
return (
<div className="App">
<AuthContextProvider>
<Router>
<Routes>
<Route path="/" element={isAuthenticated ? <Home /> : <Login />} />
<Route path="/register" element={isAuthenticated ? <Home /> : <Register />} />
</Routes>
</Router>
</AuthContextProvider>
</div>
);
}
Here's how login.js authenticates the user:
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { dispatch } = useContext(AuthContext);
const handleClick = (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const credentials = {
email: email,
password: password,
}
//Make login request to the server, and use the userID it sends back to authenticate the user
//Currently has a bug where userID is blank on first call, but works on later calls
axios.post('/auth/login', credentials)
.then(response => {
dispatch({ type: "LOGIN_SUCCESS", payload: response.data });
// console.log(userID);
// console.log(isAuthenticated);
})
.catch(err => {
console.log(err)
dispatch({ type: "LOGIN_FAILURE", payload: err });
});
}
catch(err) {
console.log(err);
}
}
Now there are 2 issues here:
In login.js, when I click the login button that calls handleClick, and I do console.log(isAuthenticated), it logs false the first time. Any time after that it will log true.
In App.js, isAuthenticated never changes to true, even while login.js will console.log it as true, so the user is never brought to the home page.
I've been struggling with this for a while now, and I just can't figure out what's going wrong here. I think it may have something to do with App.js not rerendering after the user logs in but I'm not sure.
I also have three files handling the authorization context. I don't think these are causing the issue but I will provide the code just in case I'm missing something
AuthContext.js:
import { createContext, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const initialState = {
userID: null,
isAuthenticated: false,
error: false,
}
export const AuthContext = createContext(initialState);
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, initialState);
return (
<AuthContext.Provider value={{
userID: state.userID,
isAuthenticated: state.isAuthenticated,
error: state.error,
dispatch,
}}>
{children}
</AuthContext.Provider>
)
}
AuthActions.js:
export const LoginStart = (userCredentials) => ({
type: "LOGIN_START",
});
export const LoginSuccess = (userID) => ({
type: "LOGIN_SUCCESS",
payload: userID,
isAuthenticated: true,
});
export const LoginFailure = (error) => ({
type: "LOGIN_FAILURE",
payload: error,
isAuthenticated: false,
});
AuthReducer.js:
const AuthReducer = (state, action) => {
switch(action.type) {
case "LOGIN_START":
return {
userID: null,
isAuthenticated: false,
error: false,
}
case "LOGIN_SUCCESS":
return {
userID: action.payload,
isAuthenticated: true,
error: false,
}
case "LOGIN_FAILURE":
return {
userID: null,
isAuthenticated: false,
error: action.payload,
}
default:
return state;
}
}
export default AuthReducer;
Any help is appreciated, thanks!
I figured this out, I simply had to move the AuthContext Provider out of App.js and into index.js
Implementing a Log in system with React Context API. When submitted the form with user credentials, getting an error.
Error:
Unhandled Rejection (TypeError): dispatch is not a function
loginCall
src/apiCalls.js:4
1 | import axios from "axios";
2 |
3 | export const loginCall = async (userCredential, dispatch) => {
> 4 | dispatch({ type: "LOGIN_START" });
5 | try {
6 | const res = await axios.post("auth/login", userCredential);
7 | dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
Files:
Login.jsx
import React, { useContext, useRef } from "react";
import bgImg from "../assets/login/tw-bg.png";
import "./styles/login.css";
import TwitterIcon from "#mui/icons-material/Twitter";
import { loginCall } from "../apiCalls";
import {AuthContext} from '../context/AuthContext'
function Login() {
const email = useRef();
const password = useRef();
const context = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
loginCall(
{ email: email.current.value, password: password.current.value },
context.dispatch
);
};
console.log(context.user)
return (
<div className="login-container">
<div className="left">
<TwitterIcon className="left-tw-icon" style={{ fontSize: 250 }} />
<img src={bgImg} alt="background" className="login-background" />
</div>
<div className="right">
<TwitterIcon className="right-tw-icon" color="primary" />
<div className="main-title-container">
<span className="main-title-span">Şu anda olup bitenler</span>
</div>
<div className="secondary-title-container">
<span className="secondary-title-span">Twitter'a bugün katıl.</span>
</div>
<div className="form-container">
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Username" ref={email} />
<input type="password" placeholder="Password" ref={password} />
<button type="submit">Log in</button>
</form>
</div>
</div>
</div>
);
}
export default Login;
apiCalls.js
import axios from "axios";
export const loginCall = async (userCredential, dispatch) => {
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("auth/login", userCredential);
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (error) {
dispatch({ type: "LOGIN_FAILURE", payload: error });
}
};
AuthContext.js
import { Children, createContext, useReducer } from "react";
import AuthReducer from "./AuthReducer";
const INITIAL_STATE = {
user: null,
error: null,
isFetching: false,
};
export const AuthContext = createContext(INITIAL_STATE);
export const AuthContextProvider = ({children}) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
return (
<AuthContextProvider
value={{
user: state.user,
error: state.error,
isFetching: state.isFetching,
dispatch,
}}
>
{children}
</AuthContextProvider>
);
};
Any help appreciated.
Edit: AuthReducer and AuthActions added.
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
error: null,
isFetching: true,
};
case "LOGIN_FAILURE":
return {
user: null,
error: action.payload,
isFetching: false,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
error: null,
isFetching: false,
};
}
};
export default AuthReducer
```
export const LOGIN_START = (userCredentials) => {
type:"LOGIN_START"
}
export const LOGIN_SUCCESS = (user) => ({
type:"LOGIN_SUCCESS",
payload:user
})
export const LOGIN_FAILURE = (err) => ({
type:"LOGIN_FAILURE",
})
```
Some comment to handle "mostly code error." It seems all clear to me. But the problem still continues. If there is a point I am missing, it would be great to learn from you.
Thanks in advance.
you can try this:
import axios from "axios";
export const loginCall = (userCredential) => {
return async (dispatch, getState) => {
dispatch(LOGIN_START());
try {
const res = await axios.post("auth/login", userCredential);
dispatch(LOGIN_SUCCESS(res.data);
} catch (error) {
dispatch(LOGIN_FAILURE());
}
};
in your error you see this message:
dispatch is not a function
loginCall
src/apiCalls.js:4
it is quite straight forward. It tells you what the problem is which dispatch. it is loginCall and and loginCall is in apiCalls. try to focus on the error that you see.
dispatch basically fires the function that you want to call. In your syntax, you put an object in the dispatch({some stuff}), but it actually expects you to call a function there.
I am not sure if my solution will fix everything but in the worst case, you should at least get a different error. :)
I had the same problem recently working on a similar project.
npm install cors
then in your index.js file with your express declarations,
const cors = require('cors'),
then at the very top your code but below your imports,
app.use(cors())
That will probably fix it.
Below is my component in reactjs.
import React, { useState, useEffect } from 'react';
import { Link, Redirect } from 'react-router-dom';
import { connect, useDispatch, useSelector } from 'react-redux';
import { loginUserAction } from '../actions/authenticationActions';
import { setCookie } from '../utils/cookies';
const LoginPage = () => {
const [isSuccess, setSuccess] = useState(false);
const [message, setMessage] = useState('');
const dispatch = useDispatch();
const login = useSelector(state => state.login.response);
console.log(login);
useEffect(() => {
if (login !== undefined) {
setSuccess(login.success);
setMessage(login.message);
if (isSuccess) {
setCookie('token', login.token, 1);
}
}
}, [login]);
const onHandleLogin = (event) => {
event.preventDefault();
const email = event.target.email.value;
const password = event.target.password.value;
dispatch(loginUserAction({
email, password,
}));
}
return (
<div>
<h3>Login Page</h3>
{!isSuccess ? <div>{message}</div> : <Redirect to='dashboard' />}
<form onSubmit={onHandleLogin}>
<div>
<label htmlFor="email">Email</label>
<input type="email" name="email" id="email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<div>
<button>Login</button>
</div>
</form>
Don't have account? <Link to='register'>Register here</Link>
</div>
);
};
export default LoginPage;
It logs user in. As you can see I am using hooks. When I console.log login from useSelector hook, it console's the updated state. Then the useEffect hook gets called. But the problem is the login is not updating all the time. But still useEffect goes into a loop. What am I missing and how can I fix this?
UPDATE
Below is my reducer
import * as types from '../actions';
export default function(state = [], action) {
const response = action.response;
switch(action.type) {
case types.LOGIN_USER_SUCCESS:
return { ...state, response };
case types.LOGIN_USER_ERROR:
return { ...state, response };
default:
return state;
}
};
Here is the action.
import * as types from './index';
export const loginUserAction = (user) => {
return {
type: types.LOGIN_USER,
user
}
};
A possible solution would be to destructure the object to make the comparison easier
const {message = '', success = false, token = ''} = useSelector(state => state.login.response || {}); //should prevent the error, of response is undefined
console.log(message, success);
useEffect(() => {
//there are other condition options like maybe if(message?.length)
if (message) {
setMessage(message);
}
// Can move setSuccess out of the if, to setSuccess even when it is falsy
if (success) { //note that using isSuccess here might not work cause the state might be the old one still
setSuccess(success)
setCookie('token', token, 1);
}
}, [message, success, token]); //having scalar values (string and boolean) will prevent the loop.
if (login && !isSuccess) { // Here
setSuccess(login.success);
setMessage(login.message);
if (isSuccess) {
setCookie('token', login.token, 1);
}
}
Try to add this and see if this works
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
loading: false,
error: null,
user: null,
isUserLogged: false,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
userAuthStart(state, action) {
return {
...state,
loading: true,
error: null,
user: null,
isUserLogged: false,
};
},
userAuthSuccess(state, { payload }) {
return { ...state, loading: false, user: payload, isUserLogged: true };
},
userAuthFail(state, { payload }) {
return { ...state, loading: false, error: payload, isUserLogged: false };
},
userLogout(state) {
return {
...state,
loading: false,
error: null,
user: null,
isUserLogged: false,
};
},
},
});
export const {
userAuthStart,
userAuthSuccess,
userAuthFail,
userLogout,
} = authSlice.actions;
export default authSlice.reducer;
I use #reduxjs/toolkit for redux.
You can declare and update state of isUserLogged or something to true if user is logged successfully. Then, you can use useSelector to use it inside components.
How to use
const { isUserLogged } = useSelector(state => state.auth)