I have a simple app that so far only has a main login component, and a home page with a navbar which the user is supposed to be routed to once they login to the app. The user experience should be as follows: user selects name from blue dropdown menu, once that name is selected, they click the green login button which dispatches setLoggedInUser from loggedInSlice.js and sets the selected user as the authorize object For some reason , my state from my redux store is lost when I wrap my login button in a router link tag which leads to the home page , but it is not lost when that login button doesnt link anywhere. I want to understand why I am not retaining my redux authUser state once I navigate from the userLogin.js component to the Main.js Component.
Components:
App.js
import logo from "./logo.svg";
import "./App.css";
import TestComponent from "./components/TestComponent";
import Main from "./components/Main";
import { Routes, Route, Link } from "react-router-dom";
import { useState } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";
import UserLogin from "./components/UserLogin";
import Nav from "./components/Nav";
function App() {
return (
<div className="App">
<Routes>
<Route path="home" element={<Main />} />
<Route path="/" element={<UserLogin />} />
<Route path="testcomponent" element={<TestComponent />} />
</Routes>
</div>
);
}
export default App;
UserLogin.js
import React from "react";
import { Routes, Route, Link, useNavigate } from "react-router-dom";
import { Redirect } from "react-router-dom";
import { useState, useEffect } from "react";
import { Card, Button, Accordion, Dropdown } from "react-bootstrap";
import { useSelector, useDispatch } from "react-redux";
import "bootstrap/dist/css/bootstrap.min.css";
import { setLoggedInUser } from "../features/loggedInSlice";
export default function UserLogin() {
let navigate = useNavigate();
const users = useSelector((state) => state.users);
const state = useSelector((state) => state);
const dispatch = useDispatch();
const [user, setUser] = useState("Select A User");
const [authorizedUser, setauthorizedUser] = useState({});
const handleSelect = (e) => {
setUser(e.target.text);
};
function authorizeUser() {
setauthorizedUser(
users.filter((userName) => userName.firstName === user)[0]
);
}
useEffect(() => {
dispatch(setLoggedInUser(authorizedUser));
}, [authorizedUser]);
function authorizeLogin() {
console.log(state);
}
console.log(state);
return (
<div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Card style={{ width: "50%" }}>
<Card.Header>Would you Rather?</Card.Header>
<Card.Body>
<Card.Title>Special title treatment</Card.Title>
<Card.Text>
With supporting text below as a natural lead-in to additional
content.
</Card.Text>
</Card.Body>
<Dropdown className="d-inline mx-2" onClick={handleSelect}>
<Dropdown.Toggle
id="dropdown-autoclose-true"
style={{ width: "60%" }}
>
{user}
</Dropdown.Toggle>
<Dropdown.Menu style={{ width: "60%" }}>
{users.map((user, index) => (
<Dropdown.Item href="#" key={index}>
{user.firstName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<div>
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
</div>
</Card>
<button onClick={authorizeLogin}>click</button>
</div>
</div>
);
}
Main.js
import React from "react";
import NavBar from "./Nav";
import { Routes, Route, Link } from "react-router-dom";
export default function Main() {
return (
<div>
<NavBar />
</div>
);
}
Nav.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { Card, Button, Accordion, Dropdown, Nav } from "react-bootstrap";
export default function NavBar() {
const state = useSelector((state) => state);
console.log(state);
const loggedInFirstName = state.loggedIn.authUser.firstName;
return (
<div style={{ display: "flex" }}>
<Nav justify variant="tabs" defaultActiveKey="/home">
<Nav.Item>
<Nav.Link>Home</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>New Poll </Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link>Leaderboard</Nav.Link>
</Nav.Item>
</Nav>
<h5 style={{ paddingLeft: "15px", paddingTop: "15px" }}>Welcome</h5>
</div>
);
}
Redux Store and Slices:
store.js
import { configureStore } from "@reduxjs/toolkit";
import usersReducer from "../features/usersSlice";
import loggedIn from "../features/loggedInSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
loggedIn: loggedIn,
},
});
loggedInSlice.js
import React from "react";
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
authUser: [],
};
export const loggedIn = createSlice({
name: "loggedIn",
initialState,
reducers: {
setLoggedInUser: (state, action) => {
console.log(action.payload);
state.authUser = action.payload;
},
},
});
export default loggedIn.reducer;
export const { setLoggedInUser } = loggedIn.actions;
usersSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = [
{
firstName: "matt",
age: 30,
total: 0,
},
{
firstName: "mike",
age: 25,
total: 0,
},
{
firstName: "steve",
age: 22,
total: 0,
},
];
export const usersReducer = createSlice({
name: "UsersSlice",
initialState,
});
export default usersReducer.reducer;
To summarize I need to understand why my authUser is added to my redux store only when the login button does not route to another component
code that adds the authUser
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
code that does not add the authUser
<Link to="home">
<Button
variant="success"
style={{ width: "20%", marginTop: "3%", marginBottom: "1%" }}
onClick={authorizeUser}
>
Login
</Button>
</Link>
Issue
The issue here is that enqueued React state updates are asynchronously processed. The navigation action to
"/home"
when wrapping the login button with aLink
occurs well before the enqueued state update is processed. React sees that theUserLogin
component is no longer mounted and ignores (silently) the state update.Solution
I suggest moving the "login" logic into an asynchronous action. This will return a Promise to the
UserLogin
component that can be waited on and upon successful authentication issue the navigation action to the"/home"
path.Example:
loggedInSlice:
UserLogin
Dispatch the login action directly in the button's
onClick
handler, wait for resolved Promise, then navigate.