I am new to redux. On successful login, header re-renders but then there is infinite dispatch of USER_LOADED
. I changed mapToProps from generic to specific imports but still not able to resolve this. I found one post that was referring to de-linking header from app.js but I did not get it. As in not sure what to do.
App.js
const App = () => {
useEffect(() => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
store.dispatch(loadUser());
window.addEventListener("storage", () => {
if (!localStorage.token) store.dispatch({ type: LOGOUT });
});
}, []);
return (
<Provider store={store}>
<Router>
<Header />
<ToastContainer />
<Switch>
<Route exact path="/" component={Landing} />
<Route exact path="/login" component={Login} />
{/* <Route exact path="/register" component={Register} /> */}
<Route exact path="/userList" component={UserList} />
</Switch>
</Router>
</Provider>
);
};
export default App;
Header.js
const Header = ({ user, logout }) => {
const [current, setCurrent] = useState("home");
const handleClick = (e) => setCurrent(e.key);
//const { user } = auth;
return (
<Menu onClick={handleClick} selectedKeys={[current]} mode="horizontal">
<Item key="home" icon={<HomeOutlined />}>
<Link to="/"> Training</Link>
</Item>
{!user && (
<Item key="register" icon={<UserAddOutlined />} className="float-right">
<Link to="/register">Register</Link>
</Item>
)}
{!user && (
<Item key="login" icon={<UserOutlined />} className="float-right">
<Link to="/login">Login</Link>
</Item>
)}
{user && (
<SubMenu
key="SubMenu"
icon={<LoginOutlined />}
title={(user.name && user.name) || (user.email && user.email)}
className="float-right"
>
<Item icon={<LogoutOutlined />} onClick={logout}>
Logout
</Item>
</SubMenu>
)}
</Menu>
);
};
Header.propTypes = {
user: PropTypes.object,
};
const mapStateToProps = (state) => ({
user: state.auth.user,
});
export default connect(mapStateToProps, { logout })(Header);
Login.js
const Login = ({ loading, errors, login, history }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const getin = async (e) => {
e.preventDefault();
if (!email.trim() || !password.trim()) {
toast.error("Both email and password are required");
return;
}
login({ email, password }, history);
};
return (
<div className="search-params">
<form onSubmit={getin}>
<label htmlFor="email">email</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
value={password}
placeholder="minimum 6 characters"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" disabled={!email || !password || loading}>
Submit
</button>
</form>
{errors &&
errors.map((error, index) => (
<p key={index} className="text-danger">
{error.msg}
</p>
))}
</div>
);
};
Login.propTypes = {
email: PropTypes.string,
user: PropTypes.object,
password: PropTypes.string,
};
const mapStateToProps = (state) => ({
loading: state.auth.loading,
isAuthenticated: state.auth.isAuthenticated,
errors: state.auth.errors,
});
export default connect(mapStateToProps, { login })(Login);
Reducer
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null,
errors: [],
};
const auth = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case dux.USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false,
errors: [],
};
case dux.REGISTER_SUCCESS:
case dux.LOGIN_SUCCESS:
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
errors: [],
};
case dux.REGISTER_FAIL:
case dux.LOGIN_FAIL:
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
user: null,
errors: payload,
};
case dux.AUTH_ERROR:
case dux.LOGOUT:
return {
...state,
token: null,
user: null,
isAuthenticated: false,
loading: false,
errors: [],
};
default:
return state;
}
};
export default auth;
actionCreators.js
import api from "../../utils/api";
import * as action from "./actionTypes";
export const loadUser = () => async (dispatch) => {
try {
const res = await api.get("/auth");
dispatch({
type: action.USER_LOADED,
payload: res.data,
});
dispatch(loadUser());
} catch (err) {
dispatch({
type: action.AUTH_ERROR,
});
}
};
//Login user
export const login = (formData, history) => async (dispatch) => {
try {
const res = await api.post("/auth", formData);
console.log("res in login action", res);
dispatch({
type: action.LOGIN_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
history.push("/userList");
} catch (err) {
console.log(err);
dispatch({
type: action.LOGIN_FAIL,
payload: err.response.data.errors,
});
}
};
//Register User
export const register = (formData, history) => async (dispatch) => {
try {
const res = await api.post("/users", formData);
dispatch({
type: action.REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
history.push("/userList");
} catch (err) {
console.log(err);
dispatch({
type: action.REGISTER_FAIL,
payload: err.response.data.errors,
});
}
};
export const logout = () => async (dispatch) =>
dispatch({ type: action.LOGOUT });
In loadUser , I was recursively calling it. That caused the issue. But in these hours I learned that it is always better to pass specific items from redux store to improve performance. Thank you for quick responses guys.