Maintain the global state when the route change in React Redux - reactjs

I create a app, everything works fine, but when i move route or reload the page, the state back to the default situation, maybe i able to fix this problem using library as react-persist or found some way to put the state on the local Storage, i don't think this approach is the best way.Maybe you another alternative?
`store.js`
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducer/rootReducer";
const initialState = {};
export default createStore(rootReducer, initialState, applyMiddleware(thunk));
`reducer`
import {
REGISTER_USER,
REGISTER_FAIL,
LOGIN_USER,
LOGIN_FAIL,
// LOAD_USER,
// LOAD_USER_FAIL,
// LOGOUT_USER,
} from "../constants";
const initialState = {
user: null,
loading: true,
isAuthenticated: false,
error: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case REGISTER_USER:
return {
...state,
user: action.payload,
loading: false,
isAuthenticated: true,
error: null,
};
case REGISTER_FAIL:
return {
...state,
user: null,
loading: true,
isAuthenticated: false,
error: action.payload,
};
case LOGIN_USER:
localStorage.setItem("token", action.payload.token);
return {
...state,
user: action.payload,
loading: false,
isAuthenticated: true,
error: null,
};
case LOGIN_FAIL:
return {
...state,
user: null,
loading: true,
isAuthenticated: false,
error: action.payload,
};
default:
return state;
}
};
`Login`
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Redirect, useHistory } from "react-router-dom";
import { loginAction } from "../actions/loginAction";
const Login = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.auth);
let history = useHistory();
const [msg, setMsg] = useState("");
const [state, setState] = useState({
email: "",
password: "",
});
useEffect(() => {
let interval;
if (data.error) {
setMsg(data.error.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
if (data.user) {
setMsg(data.user.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
return () => {
clearTimeout(interval);
};
}, [data]);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(loginAction(state));
};
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
if (data.isAuthenticated) {
history.push("/dashboard");
}
console.log(data);
return (
<>
<span>{msg}</span>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input name="email" onChange={handleChange} value={state.email} />
<input name="password" onChange={handleChange} value={state.password} />
<input type="submit" value="Login" />
</form>
</>
);
};
export default Login;
register
import React, { useState, useEffect } from "react";
import { registerAction } from "../actions/registerAction";
import { useDispatch, useSelector } from "react-redux";
import { Redirect } from "react-router-dom";
const Register = () => {
const dispatch = useDispatch();
const data = useSelector((state) => state.auth);
const [msg, setMsg] = useState("");
const [state, setState] = useState({
name: "",
email: "",
password: "",
photo: "",
});
useEffect(() => {
let interval;
if (data.error) {
setMsg(data.error);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
if (data.user) {
setMsg(data.user.msg);
interval = setTimeout(() => {
setMsg("");
}, 1000);
}
return () => {
clearTimeout(interval);
};
}, [data]);
if (data.user) {
return <Redirect to="/login" />;
}
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("name", state.name);
formData.append("email", state.email);
formData.append("password", state.password);
formData.append("photo", state.photo);
dispatch(registerAction(formData));
};
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
const handleOnUploadFile = (e) => {
setState({ ...state, photo: e.target.files[0] });
};
return (
<>
<span>{msg}</span>
<form onSubmit={handleSubmit}>
<label>Name</label>
<input name="name" onChange={handleChange} value={state.name} />
<label>Email</label>
<input name="email" onChange={handleChange} value={state.email} />
<label>Password</label>
<input name="password" onChange={handleChange} value={state.password} />
<input
name="photo"
type="file"
accept="image/*"
onChange={handleOnUploadFile}
/>
<input type="submit" />
</form>
</>
);
};
export default Register;

You should store a token in Local storage and pull it out and verify it upon reload. Also you should make a new action to load user from that token. States will alwayr reset to initial state on reload.
I cant see ur registerAction page but the loadUser should be something like this
// Load User
export const loadUser = () => async (dispatch) => {
try {
const res = await axios.get('/api/auth');
dispatch({
type: USER_LOADED,
payload: res.data,
} catch (err) {
dispatch({
type: AUTH_ERROR,
payload: err.response.data,
});
}
};
/api/auth is like this
// #route GET api/auth
// #desc Get logged in user (Login)
// #access Private
router.get('/', auth, async (req, res) => {
try {
const user = await Planner.findById(req.user.id).select('-password');
if (user.type === 'Admin') {
res.json({ user, admin: true });
} else {
res.json({ user, admin: false });
}
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
You need to use jwttoken for example as an auth middle ware
the middleware file would be like this
const jwt = require('jsonwebtoken');
const config = require('config');
module.exports = function (req, res, next) {
//Get token from header
const token = req.header('x-auth-token');
// Check if not token
if (!token) {
return res.status(401).json({ msg: 'No token, authorization denied' });
}
try {
const decoded = jwt.verify(token, config.get('jwtSecret'));
req.user = decoded.user;
next();
} catch (err) {
res.status(401).json({ msg: 'Token is not valid' });
}
};
I have implemented a similar redux of the contact keeper project by Brad Travery
You can see the full code here am sure it will help
https://github.com/madaher-dev/contact-keeper
Enjoy

Related

How to redirect user to dashboard after authentication in react js?

Im using react js with redux/toolkit to create user authentication.
I created a form with Formik. When the I submit the form it doesn't redirect me to the dashboard aand I'm unable to identify the issue.
I created a default authentication service under src/services/AuthService.js, the service method works coresponded useAuth fuction.
AuthService.js
export async function apiSignIn (data) {
return ApiService.fetchData({
url: '/v1/auth/login',
method: 'post',
data
})
}
And then I created a store with to Slices:
1- sessionsSlice
2- userSlice:
The logic I implemented in : src/store/auth/sessionSlice.js
import { createSlice } from '#reduxjs/toolkit'
export const sessionSlice = createSlice({
name: 'auth/session',
initialState: {
token: '',
signedIn: false,
},
reducers: {
onSignInSuccess: (state, action) => {
state.signedIn = true
state.token = action.payload
},
onSignOutSuccess: (state) => {
state.signedIn = false
state.token = ''
},
setToken: (state, action) => {
state.token = action.payload
}
},
})
export const { onSignInSuccess, onSignOutSuccess, setToken } = sessionSlice.actions
export default sessionSlice.reducer
The logic I implemented in : src/store/auth/userSlice .js
import { createSlice } from '#reduxjs/toolkit'
export const initialState = {
avatar: '',
userName: '',
email: '',
authority: []
}
export const userSlice = createSlice({
name: 'auth/user',
initialState,
reducers: {
setUser: (_, action) => action.payload,
userLoggedOut: () => initialState,
},
})
export const { setUser } = userSlice.actions
export default userSlice.reducer
The signIn logic implementation in the useAuth looks this way:
import { useSelector, useDispatch } from 'react-redux'
import { setUser, initialState } from 'store/auth/userSlice'
import { apiSignIn, apiSignOut, apiSignUp } from 'services/AuthService'
import { onSignInSuccess, onSignOutSuccess } from 'store/auth/sessionSlice'
import { REDIRECT_URL_KEY } from 'constants/app.constant'
import { useNavigate } from 'react-router-dom'
import useQuery from './useQuery'
function useAuth() {
const dispatch = useDispatch()
const navigate = useNavigate()
const query = useQuery()
const { token, signedIn } = useSelector((state) => state.auth.session)
const signIn = async (values) => {
try {
const resp = await apiSignIn(values)
if (resp.data) {
const { token } = resp.data
dispatch(onSignInSuccess(token))
if(resp.data) {
dispatch(setUser(resp.data || {
avatar: '',
userName: resp.data.userName,
authority: [resp.data.role],
email: resp.data.email
}))
}
const redirectUrl = query.get(REDIRECT_URL_KEY)
navigate(redirectUrl ? redirectUrl : '/dashboard')
return {
status: 'success',
message: ''
}
}
} catch (errors) {
return {
status: 'failed',
message: errors?.response?.data?.message || errors.toString()
}
}
}
const signUp = async (values) => {
try {
const resp = await apiSignUp(values)
if (resp.data) {
const { token } = resp.data
dispatch(onSignInSuccess(token))
if(resp.data.user) {
dispatch(setUser(resp.data.user || {
avatar: '',
userName: 'Anonymous',
authority: ['USER'],
email: ''
}))
}
const redirectUrl = query.get(REDIRECT_URL_KEY)
navigate(redirectUrl ? redirectUrl : '/dashboard')
return {
status: 'success',
message: ''
}
}
} catch (errors) {
return {
status: 'failed',
message: errors?.response?.data?.message || errors.toString()
}
}
}
const handleSignOut = () => {
dispatch(onSignOutSuccess())
dispatch(setUser(initialState))
navigate('/dashboard')
}
const signOut = async () => {
await apiSignOut()
handleSignOut()
}
return {
authenticated: token && signedIn,
signIn,
signUp,
signOut
}
}
export default useAuth
And finally, My form looks this way:
import React from 'react'
import { Input, Button, Checkbox, FormItem, FormContainer, Alert } from 'components/ui'
import { PasswordInput, ActionLink } from 'components/shared'
import useTimeOutMessage from 'utils/hooks/useTimeOutMessage'
import { Field, Form, Formik } from 'formik'
import * as Yup from 'yup'
import useAuth from 'utils/hooks/useAuth'
const validationSchema = Yup.object().shape({
userName: Yup.string().required('Please enter your username'),
password: Yup.string().required('Please enter your password'),
rememberMe: Yup.bool()
})
const SignInForm = props => {
const {
disableSubmit = false,
className,
} = props
const [message, setMessage] = useTimeOutMessage()
const { signIn } = useAuth()
const onSignIn = async (values, setSubmitting) => {
const { userName, password } = values
setSubmitting(true)
const result = await signIn({ userName, password })
if (result.status === 'failed') {
setMessage(result.message)
}
setSubmitting(false)
}
return (
<div className={className}>
{message && <Alert className="mb-4" type="danger" showIcon>{message}</Alert>}
<Formik
initialValues={{
userName: '',
password: '',
rememberMe: true
}}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
if(!disableSubmit) {
onSignIn(values, setSubmitting)
} else {
setSubmitting(false)
}
}}
>
{({touched, errors, isSubmitting}) => (
<Form>
<FormContainer>
<FormItem
label="User Name"
invalid={errors.userName && touched.userName}
errorMessage={errors.userName}
>
<Field
type="text"
autoComplete="off"
name="userName"
placeholder="User Name"
component={Input}
/>
</FormItem>
<FormItem
label="Password"
invalid={errors.password && touched.password}
errorMessage={errors.password}
>
<Field
autoComplete="off"
name="password"
placeholder="Password"
component={PasswordInput}
/>
</FormItem>
<div className="flex justify-between mb-6">
<Field className="mb-0" name="rememberMe" component={Checkbox} children="Remember Me" />
</div>
<Button block loading={isSubmitting} variant="solid" type="submit">
{ isSubmitting ? 'Signing in...' : 'Sign In' }
</Button>
</FormContainer>
</Form>
)}
</Formik>
</div>
)
}
export default SignInForm

update user password using react-router-dom v6

i want to implement update user password form using react-router-dom v6 but this code is not working..
please please.. put your suggestion or explain me about my mistakes on this code.
userReducer.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_RESET,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const profileReducer = (state = {}, action) => {
switch (action.type) {
case UPDATE_PASSWORD_REQUEST:
return {
...state,
loading: true,
};
case UPDATE_PASSWORD_SUCCESS:
return {
...state,
loading: false,
isUpdated: action.payload,
};
case UPDATE_PASSWORD_FAIL:
return {
...state,
loading: false,
error: action.payload,
};
case UPDATE_PASSWORD_RESET:
return {
...state,
isUpdated: false,
};
case CLEAR_ERRORS:
return {
...state,
error: null,
};
default:
return state;
}
};
userAction.js
import {
UPDATE_PASSWORD_REQUEST,
UPDATE_PASSWORD_SUCCESS,
UPDATE_PASSWORD_FAIL,
CLEAR_ERRORS,
} from "../Constants/userConstant";
export const updatePassword = (passwords) => async (dispatch) => {
try {
dispatch({ type: UPDATE_PASSWORD_REQUEST });
const config = { headers: { "Content-Type": "application/json" } };
const { data } = await axios.put(
`/api/v1/password/update`,
passwords,
config
);
dispatch({ type: UPDATE_PASSWORD_SUCCESS, payload: data.success });
} catch (error) {
dispatch({
type: UPDATE_PASSWORD_FAIL,
payload: error.response.data.message,
});
}
};
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
store.js
import {createStore,combineReducers,applyMiddleware} from 'redux';
import thunk from "redux-thunk";
import {composeWithDevTools} from "redux-devtools-extension";
import { profileReducer } from './Reducers/userReducer';
const reducer = combineReducers({
profile:profileReducer,
})
let initialState = {};
const middleware = [thunk];
const store = createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
UpdatePassword.js
import React, { Fragment, useState, useEffect } from "react";
import "./UpdatePassword.css";
import Loader from "../Loader/Loader";
import { useDispatch, useSelector } from "react-redux";
import { clearErrors, updatePassword } from "../../Actions/userAction";
import { UPDATE_PASSWORD_RESET } from "../../Constants/userConstant";
import {useNavigate} from 'react-router-dom'
const UpdatePassword = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { error, isUpdated, loading } = useSelector((state) => state.profile);
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const updatePasswordSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("oldPassword", oldPassword);
myForm.set("newPassword", newPassword);
myForm.set("confirmPassword", confirmPassword);
dispatch(updatePassword(myForm));
};
useEffect(() => {
if (error) {
alert(error);
dispatch(clearErrors());
}
if (isUpdated) {
alert("Profile Updated Successfully");
navigate("/account");
dispatch({
type: UPDATE_PASSWORD_RESET,
});
}
}, [dispatch, error, isUpdated]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
{/* <MetaData title="Change Password" /> */}
<div className="updatePasswordContainer">
<div className="updatePasswordBox">
<h2 className="updatePasswordHeading">Update Profile</h2>
<form
className="updatePasswordForm"
onSubmit={updatePasswordSubmit}
>
<div className="loginPassword">
<input
type="password"
placeholder="Old Password"
required
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="New Password"
required
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<input
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<input
type="submit"
value="Change"
className="updatePasswordBtn"
/>
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
export default UpdatePassword;
i want to make a form where user update user password.but due to any mistake this form is not working...

Context & Reducer not returning State

Could someone please let me know why the state isn't being updated from the reducer? The useEffect(()=>{}) isn't being triggered when the state is being returned from the reducer. I have validated the correct information is being passed to the return, but nothing can be seen from the LoginScreen.
Context Script
import React, { createContext, useReducer } from "react";
import userReducer from "./UserReducer";
export const UserContext = createContext();
const initialState = {
userData: [],
isLoggedIn: false,
isAdmin: false,
isEmployee: false,
errorMessage: [{ success: false, statusCode: 0, error: null }],
};
const UserContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
const registerUser = (user) =>
dispatch({ type: "REGISTER_USER", payload: user });
const loginUser = (user) => dispatch({ type: "LOGIN_USER", payload: user });
const deleteUser = (user) => dispatch({ type: "DELETE_USER", payload: user });
const updateUser = (user) => dispatch({ type: "UPDATE_USER", payload: user });
const contextValues = {
...state,
registerUser,
loginUser,
deleteUser,
updateUser,
};
return (
<UserContext.Provider value={contextValues}>
{children}
</UserContext.Provider>
);
};
export default UserContextProvider;
Reducer Script
import axios from "axios";
axios.defaults.withCredentials = true;
const userReducer = (state = {}, action) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
switch (action.type) {
case "REGISTER_USER":
break;
case "LOGIN_USER":
console.log(state);
const email = action.payload.email;
const password = action.payload.password;
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
// localStorage.setItem("authToken", response.data.authToken);
state.userData = response.data.user;
state.isLoggedIn = true;
if (response.data.user.role === 9) {
state.isAdmin = true;
state.isEmployee = true;
} else {
state.isAdmin = false;
state.isEmployee = false;
}
}
})
.catch((error) => {
state.errorMessage = {
success: error.response.data.success,
statusCode: error.response.status,
message: error.response.data.error,
};
});
return {
...state,
userData: [state.userData],
isLoggedIn: state.isLoggedIn,
isAdmin: state.isAdmin,
isEmployee: state.isEmployee,
errorMessage: [state.errorMessage],
};
default:
return state;
}
};
export default userReducer;
Login Form
import { useState, useEffect, useContext } from "react";
import { Link } from "react-router-dom";
import {
Button,
Form,
Grid,
Message,
Segment,
Image,
Container,
} from "semantic-ui-react";
//Custom Imports
import "./LoginScreen.css";
import Logo from "../../../img/logo.png";
//Context
import { UserContext } from "../../context/UserContext";
const LoginScreen = ({ history }) => {
const { userData, loginUser, isLoggedIn, errorMessage, clearErrorMessage } =
useContext(UserContext);
const [user, setUser] = useState({ email: "", password: "" });
const [error, setError] = useState("");
useEffect(() => {
console.log(errorMessage);
if (localStorage.getItem("authToken")) {
history.push("/dashboard");
}
}, [history]);
useEffect(() => {
if (isLoggedIn) {
console.log(userData);
console.log("User is Logged in");
// history.push("/");
}
if (!errorMessage.success && errorMessage.error != null) {
console.log(errorMessage);
setError(errorMessage.message);
setTimeout(() => {
setError("");
}, 5000);
}
}, [userData, errorMessage, isLoggedIn]);
return (
<Container className="login-container">
<Grid
textAlign="center"
style={{ height: "100vh" }}
verticalAlign="middle"
>
<Grid.Column style={{ maxWidth: 450 }}>
<Image src={Logo} className="login-logo" />
<Form size="large" onSubmit={() => loginUser(user)}>
<Segment stacked>
<Form.Input
fluid
icon="user"
iconPosition="left"
placeholder="Email Address"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
<Form.Input
fluid
icon="lock"
iconPosition="left"
placeholder="Password"
value={user.password}
type="password"
onChange={(e) => setUser({ ...user, password: e.target.value })}
/>
{error && <span>{error}</span>}
<Button color="blue" fluid size="large" type="submit">
Login
</Button>
</Segment>
</Form>
<Message>
Don't have an account? <Link to="/register">Sign Up</Link>
</Message>
</Grid.Column>
</Grid>
</Container>
);
};
export default LoginScreen;
Refactor your login function like this
const loginUser({ email, password }) => {
let config = {
header: {
"Content-Type": "application/json",
},
};
axios
.post("/api/user/login", { email, password }, config)
.then((response) => {
if (response.data.success) {
dispatch({ type: 'LOGIN_SUCCESS', payload: response.data });
}
})
.catch((error) => {
dispatch({ type: 'LOGIN_FAILED', payload: error });
});
}
and then your reducer
...
switch(action.type) {
...
case 'LOGIN_SUCCESS':
// return here a new object
// do not mutate the state (state.something = something) is not allowed
...
case 'LOGIN_FAILED':
// handle error
}
Prerequisite Reducer Concepts
Redux and useReducer use reducer like (previousState, action) => newState.
The reducer should be a 'pure' function as in this document. The promises, api calls should not be use inside reducers.
The problem:
Because you call api/promise inside the reducer. The reducer function returns the value before the promise finish. So when the promise finishes, nothing happen.
// A will be return before B, C are going to call
case "LOGIN_USER":
promiseFn()
.then(/* B */ ...)
.catch(/* C */ ...)
// A
return {
...
}
Solution:
Separate the non-pure calls from the reducer. And put them in the other code blocks (like inside hooks, event handlers...).

My alert function should clear the previous search result and then display new one

I have wriiten the below code in which the city the alert function initially works fine when a wrong city name or no city name is entered. But after the Weather details are displayed here again when I click on submit then it re renders the previous state and new one and gives both result.
Code:
import React, { FC, useState, FormEvent } from "react";
import { useDispatch } from "react-redux";
import { Header, Input, Button } from "../style";
import {
getWeather,
setLoading
} from "../../store/actions/WeatherAction/weatherActions";
import { setAlert } from "../../store/actions/AlertAction/alertActions";
interface SearchProps {
title: string;
}
const Search: FC<SearchProps> = ({ title }) => {
const dispatch = useDispatch();
const [city, setCity] = useState("");
const changeHandler = (e: FormEvent<HTMLInputElement>) => {
setCity(e.currentTarget.value);
};
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
dispatch(setLoading());
dispatch(getWeather(city));
setCity("");
};
return (
<>
<Header>
{title}
<form onSubmit={submitHandler}>
<Input
type="text"
placeholder="Enter city name"
value={city}
onChange={changeHandler}
/>
<br />
<Button>Search</Button>
</form>
</Header>
</>
);
};
export default Search;
weatherAction.ts
import { ThunkAction } from "redux-thunk";
import { RootState } from "../..";
import {
WeatherAction,
WeatherData,
WeatherError,
GET_WEATHER,
SET_LOADING,
SET_ERROR
} from "../../types";
export const getWeather = (
city: string
): ThunkAction<void, RootState, null, WeatherAction> => {
return async (dispatch) => {
try {
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=3020950b62d2fb178d82816bad24dc76`
);
if (!res.ok) {
const resData: WeatherError = await res.json();
throw new Error(resData.message);
}
const resData: WeatherData = await res.json();
dispatch({
type: GET_WEATHER,
payload: resData
});
} catch (err) {
dispatch({
type: SET_ERROR,
payload: err.message
});
}
};
};
export const setLoading = (): WeatherAction => {
return {
type: SET_LOADING
};
};
export const setError = (): WeatherAction => {
return {
type: SET_ERROR,
payload: ""
};
};
weatherReducer
import {
WeatherState,
WeatherAction,
GET_WEATHER,
SET_LOADING,
SET_ERROR
} from "../../types";
const initialState: WeatherState = {
data: null,
loading: false,
error: ""
};
export default (state = initialState, action: WeatherAction): WeatherState => {
switch (action.type) {
case GET_WEATHER:
return {
data: action.payload,
loading: false,
error: ""
};
case SET_LOADING:
return {
...state,
loading: true
};
case SET_ERROR:
return {
...state,
error: action.payload,
loading: false
};
default:
return state;
}
};
The problem is that your reducer does not clear the weather data when processing a SET_ERROR action. If you want to clear the weather data when you receive an error, you should set data back to null like this:
case SET_ERROR:
return {
data: null,
error: action.payload,
loading: false
};

UseEffect not fired when redux props change caused by async api call with redux-thunk

I have a functional login page connected with redux, I'm firing an async event onSubmit that will trigger the emailLogin action, I am using useEffect to detect the change of the isLoading prop to see whether login finished or not. If login success, the redux store should have the user object, if failed, the user should remain null.
The question is, I know that the login is success, which should triggered the change of isLoading, the parameter that decide whether the useEffect, however, the useEffect is not fired. Also, the console.log('done'); after the line await emailLogin(authData); is never fired. Ssomething is wrong.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { emailLogin } from '../actions/index';
function Login({ user, isLoading, emailLogin }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const history = useHistory();
useEffect(() => {
console.log('useEffect fired', user, isLoading); //<-----This does not fire after login success
if (user) {
history.push('/protected_home');
}
}, [isLoading]);
const submitEmailLoginForm = async (e) => {
e.preventDefault();
const authData = { email, password };
await emailLogin(authData);
console.log('done'); // <------- This is never fired
};
return (
<div>
<h2>Login</h2>
<Link to="/">back</Link>
<form onSubmit={submitEmailLoginForm}>
<label>
email:
<input
type="text"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
password:
<input
type="text"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}
const mapStateToProps = (state) => ({
user: state.user,
isLoading: state.isLoading
});
const mapDispatch = {
emailLogin: emailLogin
};
export default connect(mapStateToProps, mapDispatch)(Login);
My action file:
import axios from 'axios';
export const authActions = {
EMAIL_LOGIN_START: '##EMAIL_LOGIN_START',
EMAIL_LOGIN_SUCCESS: '##EMAIL_LOGIN_SUCCESS'
};
export const emailLogin = ({ email, password }) => async (dispatch) => {
dispatch({ type: authActions.EMAIL_LOGIN_START });
try {
const response = await axios.post('http://localhost:5001/api/auth', {
email: email,
password: password
});
dispatch({
type: authActions.EMAIL_LOGIN_SUCCESS,
payload: {
user: { ...response.data }
}
});
} catch (error) {
console.log('Should dispatch api error', error.response);
}
};
My Reducer:
import { authActions } from '../actions/index';
const initialState = {
user: null,
isLoading: false
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case authActions.EMAIL_LOGIN_START:
return { ...state, isLoading: true };
case authActions.EMAIL_LOGIN_SUCCESS:
console.log('Reducer check => Login is success'); //<-----this line is printed
return { ...state, user: action.payload.user, isLoading: false };
default:
return state;
}
};
export default userReducer;
In the reducer, I see that the success action is actually triggered by checking the console.log(). Also in the redux dev tool, I can actually see that the login is success and the isLoading prop has changed :
This solve my problem
const mapStateToProps = (state) => ({
user: state.userReducer.user,
isLoading: state.userReducer.isLoading
});

Resources