0

I'm very new to React & Redux and am working on the authentication portion of my app.

The way my backend API works is, once a user signs in, the response contains:

  • The JWT token to use for future requests
  • An object with the User's data

What I would like to do is have the following occur:

  1. Store the token in localstorage
  2. Set a variable isAuthenticated in Redux to true (false for logout)
  3. Store the user information in a second Redux variable called user

I've currently the following Action:

import { SIGN_IN, SIGN_OUT } from "./Types";

export const signIn = response => {
    return {
        type: SIGN_IN,
        payload: response
    };
}

export const signOut = () => {
    return {
        type: SIGN_OUT
    };
}

and the following Reducer:

import { SIGN_IN, SIGN_OUT } from './Types';

const INITIAL_STATE = {
    isAuthenticated: null,
    user: {}
};

const authReducer = (state = INITIAL_STATE, action) => {
    switch(action.type) {
        case SIGN_IN:
            // This logic was removed thanks to comment from Sanish Joseph
            // localStorage.setItem("token", action.payload.token);

            return {...state, isAuthenticated: true, user: action.payload.user};
        case SIGN_OUT:
            // This logic was removed thanks to comment from Sanish Joseph
            // localStorage.removeItem("token");

            return {...state, isAuthenticated: false, user: {}};
        default:
            return state;
    };
};

export default authReducer;

and CombineReducers code:

export default combineReducers({
    ...
    auth: authReducer
});

This code works, but both user and isAuthenticated are children of auth (in other words, I have to get them both and refer to them via auth.user & auth.isAuthenticated.

What I don't know how to do is how to write my reducer code so SIGN_IN will still send all the data I got from the API, but be able to create 2 separate pieces of state in Redux isAuthenticated & user.

Any help would really be appreciated!

John Bustos
  • 19,036
  • 17
  • 89
  • 151
  • 1
    You will have an action where you call your API. You may use Thunk to call your API. There when you get back response you trigger 2 actions. One for the `isAuthenticated` and other for user object. Your local storage logic shouldn't be in the reducer. Move that to your component. – Sanish Joseph Sep 17 '21 at 22:17
  • @SanishJoseph, I completely understand your logic re: the `localstorage` and already updated my code - Thank you so much for that!!! As for the other part re: triggering 2 actions, is that the only / correct way to do it rather than just having one action for `LOG_IN` and a second for 'LOG_OUT` - It seemed more intelligent to me that that should be the only action called and the logic would be split up afterwards. Or am I misunderstanding. – John Bustos Sep 17 '21 at 22:22
  • 1
    In your case `isAuthenticated` and `user` are 2 items in your redux store. You have named part of your store which you want to manage separately as `auth`. In your component when you want to access 1 item you can do it like, `const {isAuthenticated ,user}= useSelector( (state: any) => state.auth );` – Sanish Joseph Sep 17 '21 at 22:31
  • 1
    Since both `isAuthenticated ,user` are related what you are doing is correct. If it was a different part of state which is not related to authentication, you can create another reducer and add that in the `combineReducers` with it's name. So you get 1 store but different pieces of state that can be easily accessed with `useSelector` . – Sanish Joseph Sep 17 '21 at 22:34
  • 1
    If you want to move the `user` to a separate part of store and not `auth` then create a new reducer for that and add in the combineReducers. – Sanish Joseph Sep 17 '21 at 22:36
  • Thanks again, @SanishJoseph, I think that last comment of yours is exacly what I'm trying to figure out how to do - What I'm imagining is to call 2 different reducers from within my `authReducer`, so I'd, from my code call `authReducer` with `LOG_IN` and it would, then, split up the work into the 2 separate reducers. Does that make sense / is there any way to do that that is considered good coding practice? – John Bustos Sep 17 '21 at 22:46
  • 1
    Nope. Any action can be handled by multiple reducers. So, you trigger an action and your auth reducer will handle the `isAuthenticated` and user reducer will handle the `user` object. – Sanish Joseph Sep 17 '21 at 22:49
  • Every action call goes through all of your reducers. If you are not handling it with your switch cases, it's gonna reach the default and return the portion of the state untouched.. – Sanish Joseph Sep 17 '21 at 22:51
  • That idea of 2 separate reducers makes perfect sense!!! - So I just create an `authReducer` and a `userReducer` - THANK YOU!!!! – John Bustos Sep 17 '21 at 22:59
  • If you'd like to post that as an answer, I'll happily accept it. – John Bustos Sep 17 '21 at 22:59
  • 1
    I will summarize everything and post as an answer. – Sanish Joseph Sep 17 '21 at 23:00

1 Answers1

2

So what I understood from the question is, you want to make isAuthenticated and user as 2 separate parts in the store.

For separating the state into multiple parts, you will need multiple reducers. Another important logic to remember is, all of your reducers will get called when any action is dispatched. It's your decision whether to handle a particular action or not. Multiple reducers can handle same action.

So to create 2 parts one for auth and one for user, you can create 2 reducers. Remove user handling from auth reducer and add that to user reducer. Action is going to be the same. So in auth reducer,

import { SIGN_IN, SIGN_OUT } from './Types';

const INITIAL_STATE = {
    isAuthenticated: null,
    
};

const authReducer = (state = INITIAL_STATE, action) => {
    switch(action.type) {
        case SIGN_IN:           

            return {...state, isAuthenticated: true};
        case SIGN_OUT:          

            return {...state, isAuthenticated: false};
        default:
            return state;
    };
};

export default authReducer;

and your user reducer will look like,

import { SIGN_IN, SIGN_OUT } from './Types';

const INITIAL_STATE = {    
    user: {}
};

const userReducer = (state = INITIAL_STATE, action) => {
    switch(action.type) {
        case SIGN_IN:
            

            return {...state,  user: action.payload.user};
        case SIGN_OUT:
           

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

export default userReducer;

and don't forget to combine them,

export default combineReducers({
    ...
    auth: authReducer,
    user:userReducer
});
superjos
  • 12,189
  • 6
  • 89
  • 134
Sanish Joseph
  • 2,140
  • 3
  • 15
  • 28