0

Hello I have a react redux store where I store the info about the currently logged in user. Here is my reducer and my actions:

interface Account {
    username: string | null;
    isLoggedIn: boolean;
    role: string | null;
}

const accountReducer = (state: Account, action: any): Account | {} => {
    switch (action.type) {
        case "SET_ACCOUNT":
            return {
                username: action.payload.username,
                isLoggedIn: action.payload.isLoggedIn,
                role: action.payload.role,
            };

        case "LOGOUT_ACCOUNT":
            return {
                username: null,
                isLoggedIn: false,
                role: null,
            };

        default:
            return { ...state };
    }
};

export default accountReducer;
interface Account {
    username: string | null;
    isLoggedIn: boolean;
    role: string | null;
}

const setAccount = (account: Account) => {
    return {
        type: "SET_ACCOUNT",
        payload: account,
    };
};

const logoutAccount = () => {
    return {
        type: "LOGOUT_ACCOUNT",
        payload: {
            username: null,
            isLoggedIn: false,
            role: null,
        },
    };
};

export default { setAccount, logoutAccount };

And I have a logout button which calls the following function:

const logoutUser = async (): Promise<void> => {
        await axios({
            method: "POST",
            url: API.logout,
            withCredentials: true,
            headers: {
                "X-CSRF-TOKEN": csrf.token,
            },
        });
        dispatch(allActions.accountActions.logoutAccount());
    };

But now when I click the logout button, I get logged out, but none of my other components which are accessing the account properties are getting updated. I tried putting window.location.reload() under the dispatch function, but that isn't refreshing my window for some reason. I would also like to avoid refreshing the window if possible, since this is a SPA.

Here's an example of one of my components which need to be updated:

import React from "react";
import { NavLink, Link } from "react-router-dom";
import { useSelector } from "react-redux";

import "../../css/Navbar.css";
import LoggedInDiv from "../account/LoggedInDiv";
import LoginRegisterDiv from "../account/LoginRegisterDiv";

const Navbar: React.FC = () => {
    const account = useSelector((state: any) => state.accountReducer);

    return (
        <div className="main-navbar-container">
            <div className="navbar-logo-container">
                <h1 style={{ padding: "0px", margin: "0px" }}>
                    <Link to="/" className="navbar-link">
                       Logo
                    </Link>
                </h1>
            </div>
            <div style={{ display: "flex", flexDirection: "column" }}>
                <div className="navbar-links-container">
                    <NavLink to="/builder" className="navbar-link" activeClassName="navbar-link-active">
                        Builder
                    </NavLink>
                    <NavLink
                        to="/finishedbuilds"
                        className="navbar-link"
                        activeClassName="navbar-link-active"
                    >
                        Finished Builds
                    </NavLink>
                    <NavLink to="/about" className="navbar-link" activeClassName="navbar-link-active">
                        About
                    </NavLink>
                    <NavLink to="/user/login" className="navbar-link" activeClassName="navbar-link-active">
                        Contact
                    </NavLink>
                </div>
                <div style={{ marginLeft: "21px", marginTop: "6px" }}>
                    <div className="account-container">
                        {account.isLoggedIn ? (
                            <LoggedInDiv username={account.username} />
                        ) : (
                            <LoginRegisterDiv />
                        )}
                    </div>
                </div>
            </div>
        </div>
    );
};

export default Navbar;

Okay so I finally found the problem. My axios POST request to /logout was actually returning an error and crashing, so I never even got to the dispatch after the request. I just wrapped the axios code in a try catch block until I find why is my backend giving me errors when I try to logout, even though it logs me out successfully.

randomboiguyhere
  • 525
  • 5
  • 17
  • How are the other components accessing the redux store? – Trisma Feb 10 '20 at 14:17
  • @Trisma `const account = useSelector((state: any) => state.accountReducer);` – randomboiguyhere Feb 10 '20 at 14:18
  • So I guess that even if you log `account`, you won't see the logging a second time if you click on the logout button... Seems like I'm having this issue as well with another global state system that uses hooks. – Trisma Feb 10 '20 at 14:20
  • @randomboiguyhere : it should've been `const account = useSelector((state: any) => state.username)` if you're tracking `username` changes, you may replace it with `isLoggedIn` if login status is of interest. – Yevhen Horbunkov Feb 10 '20 at 14:27
  • @YevgenGorbunkov but I need to access the `isLoggedIn` property also, but I tried changing to your code and it didn't help – randomboiguyhere Feb 10 '20 at 14:30
  • than simply do `const account = useSelector(({username, isLoggedIn,role}) => ({username, isLoggedIn,role}))` to return all of those properties – Yevhen Horbunkov Feb 10 '20 at 14:32
  • @YevgenGorbunkov okay but what's wrong with my code? Neither mine nor your code fixes the problem... – randomboiguyhere Feb 10 '20 at 14:34
  • You might want to check out [my answer](https://stackoverflow.com/a/60152768/11299053) extended with live-snippet that emulates behavior you're trying to build – Yevhen Horbunkov Feb 10 '20 at 14:49

2 Answers2

0

Okay so I finally found the problem. My axios POST request to /logout was actually returning an error and crashing, so I never even got to the dispatch after the request. I just wrapped the axios code in a try catch block until I find why is my backend giving me errors when I try to logout, even though it logs me out successfully.

randomboiguyhere
  • 525
  • 5
  • 17
-1

First of all, you should always set your state to an initial state

const INITIAL_STATE = {
    username: null,
    isLoggedIn: false,
    role: null
}

And then in your reducer you set it to state: Account = INITIAL_STATE

This is to prevent your store from returning undefined before it's populated.

It is also a good practice to use constant types for your actions, to avoid misspelling them in either the reducer or action.

When it comes to the components not updating, I suspect that the components isn't correctly connected to the redux store, and if they don't get notified that the store has changed (e.g. new props, or local state being changed), they wont re-render.

It's hard to tell where the actual problem is, without seeing the actual code. But you should check all components that need to re-render, to see if they actually receives the updated redux state.

Edit:

const Navbar: React.FC = (props) => {
    const account = props.account;

    return ( your code );

};

const mapStateToProps = (state: any) => {
    return state.accountReducer;
};

export default connect(mapStateToProps)(Navbar);
Sodnarts
  • 371
  • 2
  • 10
  • This don't really explain what the problem is. It's obvious that the redux store is not connected correctly, otherwise he wouldn't have posted it here. But even with the file structure he gave in the question it's not clear what is going wrong – Trisma Feb 10 '20 at 14:26
  • I am fully aware of that, but like you said, it's not possible to tell the exact cause of the issue without seeing the entire code. So I explained what I thought could be the issue, to point in one direction. It does help with answering that the problem isn't in the reducer or actions themselves, which I highly doubt, and rather in the component being incorrectly wired up to the store. How would you explain the problem with the information provided? – Sodnarts Feb 10 '20 at 14:36
  • @Sodnarts I added a example of one of my components that need to be re rendered on store update, check my OP. – randomboiguyhere Feb 10 '20 at 14:40
  • I haven't worked much with useSelector, but what is the output if you console.log the state in that component? It does not seem to be connected to the store at all. You should create a mapStateToProps = (state: any) => state.accountReducer And then you should use connect(mapStateToProps)(Navbar); This should give you the option to use props.accountReducer which is the object in the store, since the component is now connected to the store – Sodnarts Feb 10 '20 at 14:46
  • @randomboiguyhere I updated my answer to make the format a bit better, but that is one way of connecting your component to the redux store – Sodnarts Feb 10 '20 at 14:50
  • @Sodnarts : assuming OP is (*obviously*) using hooks within his codebase, I don't think suggesting *classical* `connect(mapStateToProps)` makes much sense, especially using line `return state.accountReducer` that will, most probably, result in the same problem OP is currently having. – Yevhen Horbunkov Feb 10 '20 at 14:53
  • I agree, but that is the way I have done it, it should work. I am not very experienced in hooks. But whenever I use functional components, I connect it this way, even while I use hooks like useState, useEffect, etc. I have no issues using the classical connect function. If he prefers using hooks entirely he could, I only offered the one solution I know of. – Sodnarts Feb 10 '20 at 14:56
  • @Sodnarts please check my comment on the other answer, I answer your question there – randomboiguyhere Feb 10 '20 at 15:05
  • @randomboiguyhere Okay, when you press logout button, do the console.log print another line instantly? Or is it only when you refresh? If it does in fact print another log when you click logout, it should work. But since you're getting the data anyways, I really can't see what is causing the issue. – Sodnarts Feb 10 '20 at 15:12
  • @Sodnarts : I'm guessing (can't tell for sure, since you didn't provide live snippet to prove your solution is viable) that even with `connect()` approach you should have done `return state` (instead of `return state.accountReducer`)within `mapStateToProps()` body. – Yevhen Horbunkov Feb 10 '20 at 15:21
  • Yes, by the way his reducers are set up it should just be state. – Sodnarts Feb 10 '20 at 17:02