display alert errors when form is submitted - javascript

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.

Related

localStorage persist data in react MERN Stack App

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.

React context API authentication not working

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

why dispatch not return any value in react and redux?

I try to create login using React and redux, login api working fine and return success, but the dispatch not returing any thing, it shows some error, look out the below codes
Login.js
const handleLogin=(e)=>{
e.preventDefault();
setLoading(true);
dispatch(login(userName, password))
.then((response)=>{
console.log(response)
props.history.push("/masters/dealerMaster");
window.location.reload();
})
.catch((error)=>{
console.log('h', error);
setLoading(false);
})
};
Action.js
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
SET_MESSAGE
} from './types';
import AuthService from '../services/auth.services';
export const login = (userName, password)=>(dispatch) =>{
return AuthService.login(userName, password).then(
(data)=>{
dispatch({
type: LOGIN_SUCCESS,
payload : {user : data},
});
return Promise.resolve();
},
(error)=>{
const message = (
error.message &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL
});
dispatch({
type: SET_MESSAGE,
payload : message
});
return Promise.reject();
}
);
};
export const logout=()=> (dispatch)=>{
AuthService.logout();
dispatch({
type : LOGOUT
});
}
Reducer > auth.js
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT
} from '../actions/types';
const user = JSON.parse(localStorage.getItem('user'));
const initalState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user : null};
export default function(state = initalState, action){
const {type, payload} = action;
switch (type) {
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn : true,
user : payload.user
};
case LOGIN_FAIL :
return {
...state,
isLoggedIn : false,
user : null
}
case LOGOUT :
return {
...state,
isLoggedIn : false,
user : null
}
default:
return state;
}
}
Below are the errors i've received.
If we refresh the page it will redirect the next page.
what is the major issue i made
Please help me out im new to React and Redux.

useEffect hook is turning into infinite loop even when the dependency is not changing all the time

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)

REACT/REDUX Action not getting dispatched

What I am tying to do is when the user clicks on sign in button my action gets dispatch with email and password.
But, my action is not getting dispatched. Like when I checked my redux-dev-tools it is not showing anything:
There are no error message in console. I checked other answer's but nothing helped.
Here is the source code:
LoginScreen.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import ErrorMessage from "../../components/ErrorMessage/ErrorMessage";
import Loader from "../../components/Loader/Loader";
import { login } from "../../redux/actions/userActions";
import "./LoginScreen.scss";
const LoginScreen = ({ location, history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const dispatch = useDispatch();
const userLogin = useSelector((state) => state.userLogin);
const { loading, error, userInfo } = userLogin;
const redirect = location.search ? location.search.split("=")[1] : "/";
useEffect(() => {
if (userInfo) {
history.push(redirect);
}
}, [history, userInfo, redirect]);
const submitHandler = (e) => {
e.preventDefault();
dispatch(login(email, password));
};
return (
<>
<div className="login-container">
<div className="login-form">
<h1>Login</h1>
{loading ? (
<Loader />
) : error ? (
<ErrorMessage error={error} />
) : (
<form onSubmit={submitHandler}>
<div className="login-form-items">
<input
className="login-input"
type="email"
placeholder="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="login-input"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" value="submit">
Login
</button>
<h4>OR</h4>
<div className="login-form-social">
<button className="social">
<img
className="googleLogo"
src="/logo/google.svg"
alt="G"
/>{" "}
Login with Google
</button>
<button className="social social-github">
<img
className="githubLogo"
src="/logo/github.svg"
alt="GH"
/>{" "}
Login with GitHub
</button>
</div>
</div>
</form>
)}
</div>
</div>
</>
);
};
export default LoginScreen;
userAction.js
import axios from "axios";
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
} from "../constants/userConstants";
export const login = () => (email, password) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
"Content-Type": "appllication/json",
},
};
const { data } = await axios.post(
"/api/users/login",
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
userReducer.js
import {
USER_LOGIN_FAIL,
USER_LOGIN_REQUEST,
USER_LOGIN_SUCCESS,
USER_LOGOUT,
} from "../constants/userConstants";
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true };
case USER_LOGIN_SUCCESS:
return { loading: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { loading: false, error: action.payload };
case USER_LOGOUT:
return {};
default:
return state;
}
};
store.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
// reducers
import { userLoginReducer } from "./reducers/userReducers";
const reducer = combineReducers({
userLogin: userLoginReducer,
});
const userInfoFromStorage = localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
You've defined your action wrong. With redux-thunk you define your actions like this:
export const login = (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = (email, password) => {
return async (dispatch) => {
// your action code
}
}
Not like this:
export const login = () => (email, password) => async (dispatch) => {
// your action code
};
// The above code is equivalent to
export const login = () => {
return (email, password) => {
return async (dispatch) => { // this is wrong
}
}
}
So your action is returning a function which then returns another function.
The way you use it caught my attention. Out of general use. Generally, api operations are done with packages such as saga or thunk. Action is only used as a hyperlink. I suggest you review the article below. I think this build will solve your problem.
https://blog.devgenius.io/reactjs-simple-understanding-redux-with-redux-saga-f635e273e24a

Categories

Resources