Search code examples
reactjsreduxreact-reduxaxiosredux-thunk

Why does redux only update on browser refresh?


I've been on this for a long time. I've moved things around in what seems like every possible way. After searching on overflow I haven't been able to piece a solution from any other questions posed by other users.

I have an app that lets users log in. I'm using redux to store the user's credentials AFTER they log in to receive their JWT. I want to be able to log in and immediately see myself logged in. But this only happens after I login after I refresh the browser.

So in react I have a login form that calls the function below after doing all the typical features of collecting user information (login and password). Notice that I need dispatch there even though I'm not actually using it because otherwise I will get an error saying "actions must be plain objects".

At the end of that promise I call getUser to take that JWT and return from the server with all of their information on that account. In the debugger, it runs through the login function and then the getUser function but never seems to store it in redux unless I refresh the browser.

export const loginUser = (credentials) => dispatch => {
    axios.post('api_call', credentials)
        .then(res => {
            debugger;
            const token = res.data.token;
            localStorage.setItem('token', token);
            alert("You have logged in successfully!");
            getUser(token);
        });
}
export const getUser = (token) => dispatch => {
    debugger;
    axios.get('api_call', {
        headers: {"Authorization" : `Bearer ${token}`}
    }).then(user => dispatch({
            type: GET_USER,
            payload: [user.data]
        }));   
}

Let me also show you my reducer:

import { LOGIN_USER, REGISTER_USER, GET_USER, LOGOUT_USER } from "../actions/types";

const initialState = {
    items: []
}

export default function(state = initialState, action){
    switch(action.type){
        case REGISTER_USER:
            return{
                ...state,
                items: action.payload
            };
        // case LOGIN_USER:
        //     return{
        //         ...state,
        //         items: action.payload
        //     };
        case GET_USER:
            debugger;
            return{
                ...state,
                items: action.payload
            };
        case LOGOUT_USER:
            return{
                ...state,
                items: []
            }
        default:
            return state;
    }
}

I have the LOGIN_USER commented out because I realize I'm not actually dispatching anything to the store, rather, just saving JWT to local storage.

Let me also mention what I've tried. I thought maybe getUser couldn't get called in the way that I have tried. So on a nav link that redirects to home I tried putting the getUser call there. I tried putting the getUser in a useEffect hook on the Home component to mimic componentDidMount. I tried calling getUser directly after calling loginUser in the actual login form. That doesn't work because of async (the local storage doesn't have the jwt by that time). I tried changing parts of my reducer.

Here are my root reducer and store for good measure. Auth reducer pertains to all login, logout, and registration functionality.

import { combineReducers } from "redux";
import showReducer from './showReducer';
import gearReducer from './gearReducer';
import authReducer from './authReducer';
import musicReducer from './musicReducer';
import profileReducer from './profileReducer';
import bagReducer from './bagReducer';

export default combineReducers({
    shows: showReducer,
    gear: gearReducer,
    auth: authReducer,
    music: musicReducer,
    profile: profileReducer,
    bag: bagReducer
});
import { createStore, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';
import { composeWithDevTools } from 'redux-devtools-extension';

//initial app state
const initialState = {};

//middleware
const middleware = [thunk];

//...middleware (spread operator) because we want it to be added on to the middleware array
const store = createStore(
    rootReducer, 
    initialState, 
    composeWithDevTools(
        applyMiddleware(...middleware)
        )
    );

export default store;

Let me also mention that when I do refresh the page the debugger goes through a componentDidMount function for the main app that decodes jwt and calls getUser.

I believe that's all the relevant code but I can update if needed. Thank you in advance and sorry for the shoddy question.


Solution

  • From what I can see you aren't dispatching your getUser action to the store.

    Even when within other actions, you still need to actually dispatch actions to your redux store if you want them to be processed. getUser by itself is just an action creator function, and in this case, an asynchronous action creator that returns a function that still needs to be dispatched to the store.

    export const loginUser = (credentials) => dispatch => {
      axios.post('api_call', credentials)
        .then(res => {
          const token = res.data.token;
          localStorage.setItem('token', token);
          alert("You have logged in successfully!");
          dispatch(getUser(token)); // <-- dispatch getUser action!
        });
    }