Search code examples
reactjsreduxreact-reduxredux-toolkit

Why am i getting an action rejected in the first instance but then fullfiled?


I am having issues with my login functionality in my front end. I am sure that this is caused in my frontend due that in postman all my data flow works fine.

I have a login component that captures the email and password from the form and dispatches an action with the data:

import React, { useState }  from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { loginUser } from '../../slices/authSlice';
import '../../styles/components/Auth/LoginForm.css';
import { BrowserRouter , Navigate } from 'react-router-dom';


const LoginForm = () => {
  
  const dispatch = useDispatch();

  const [formData, setFormData] = useState({
    email: "",
    password: ""
  });

  const {email, password } = formData;

  const onChange = (e) =>
    setFormData({ ...formData, [e.target.name]: e.target.value });

  const onSubmit = (e) => {
    e.preventDefault();
      dispatch(loginUser({email, password}));
  };

  return (
    <form action="" className="login__form" onSubmit={(e) => onSubmit(e)}>
      
      <div className="field-container">
        <label>Email</label>
        <input
          type="email"
          name="email"
          value={email}
          onChange={(e) => onChange(e)}
        />
      </div>
      
      <div className="field-container">
        <label>Password</label>
        <input
          type="password"
          name="password"
          id=""
          value={password}
          onChange={(e) => onChange(e)}
        />
      </div>
      
      <button type="submit">Log In</button>
    </form>
  );
};

export default LoginForm;

Later i have my authSlice with different methods:

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from 'axios';
import setAuthToken from "../utils/setAuthToken";

const initialState = {
    token: localStorage.getItem('token'),
    isAuthenticated: false,
    isLoading: true,
    user: null
}

export const loadUser = createAsyncThunk(
    'auth/loadUser',
    async (_,{ rejectWithValue }) => {
  
      if(localStorage.token){
        setAuthToken(localStorage.token);
      }
  
      try {
        const res = await axios.get('http://localhost:5000/api/auth/me');
        return res.data;
      } catch (error) {
        return rejectWithValue(error.response.data);
      }
    }
  )
  
  export const registerUser = createAsyncThunk(
    'auth/registerUser',
    async (newUser, { dispatch, rejectWithValue }) => {
      try {
        const config = {
          headers: {
            'Content-Type': 'application/json',
          },
        };
        const body = JSON.stringify(newUser);
        const response = await axios.post(
          'http://localhost:5000/api/auth/register',
          body,
          config
        );
        dispatch(loadUser(newUser));
        return response.data;
      } catch (error) {
        return rejectWithValue(error.response.data);
      }
    }
  );
  
  
  export const loginUser = createAsyncThunk('auth/login', async ({ email, password }, { dispatch, rejectWithValue }) => {
    const body = { email, password };
  
    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
    };
  
    try {
      const res = await axios.post('http://localhost:5000/api/auth/login', body, config);
  
      dispatch(loadUser(res.data));
      console.log(body)
      return res.data;
    } catch (err) {
      return rejectWithValue(err.response.data);
    }
  });




export const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers:{
        logout:(state, action) => {
            state.token = null;
            localStorage.removeItem('token');
            state.isAuthenticated = false;
            state.user = null;
        }
    },
    extraReducers: (builder) => {
        builder
            .addCase(loadUser.pending, (state) => {
                state.isLoading =true;
            })
            .addCase(loadUser.fulfilled, (state, action) => {
                state.isLoading = false;
                state.isAuthenticated = true;
                state.user = action.payload;
            })
            .addCase(loadUser.rejected, (state, action) => {
                state.isLoading = false;
                state.isAuthenticated = false;
                console.log(action.payload);
            })
            .addCase(registerUser.pending, (state) => {
                state.isLoading = true;
            })
            .addCase(registerUser.fulfilled, (state, action) => {
                state.token = localStorage.setItem('token', action.payload.token);
                state.isLoading = false;
                state.isAuthenticated = true;
                state.user = action.payload;
            })
            .addCase(registerUser.rejected, (state, action) => {
                state.isLoading = false;
                /* state.isAuthenticated = false; */
                console.log(action.payload);
            })
            .addCase(loginUser.pending, (state) => {
                state.isLoading = true;
            })
            .addCase(loginUser.fulfilled, (state, action) => {
                state.isAuthenticated = true;
                state.isLoading = false;
                state.user = action.payload;
                state.token = localStorage.setItem('token', action.payload.token);
            })
            .addCase(loginUser.rejected, (state, action) => {
                state.isAuthenticated = false;
                state.isLoading = false;
                state.user = null;
                console.log(action.error);
                
              }
            )
    }
});

export const { logout } = authSlice.actions;

export default authSlice.reducer;

And this is my util function setAuthToken.js:

import axios from 'axios';

const setAuthToken = (token) => {
  if (token) {
    axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  } else {
    delete axios.defaults.headers.common['Authorization'];
  }
  console.log(axios.defaults.headers.common['Authorization']);
};


export default setAuthToken;

The issue is the following. When i submit the form in my redux devtools i get auth/login/fullfilled with the correct data. But the auth/loaduser is rejected and i gete the following data:

type(pin):"auth/loadUser/rejected"
success(pin):false
error(pin):"Not authorized to access this route"
success(pin):true
token(pin):"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkNzEzOTk1YjcyMWMzYmIzOGMxZjVkNSIsImlhdCI6MTY4NjA4NDA0MiwiZXhwIjoxNjg2MDg1MDQyfQ.QorBCHQW5QBXdQHG0HD3fXLWsWX3aOVQyD8uKmEZMoI"
requestId(pin):"NLUh3rFIYsFilYjBGkbmK"
rejectedWithValue(pin):true
requestStatus(pin):"rejected"
aborted(pin):false
condition(pin):false
message(pin):"Rejected"

I dont know why do i get rejected if the token is valid.

So my login is buggy.

In the first login i get auth/login/fullfilled and auth/loaduser/rejected but the token is set correctly in my localstorage.

Now that the token is set if i try to login again, i get both actions fulffiled.

Why am i getting auth/login/fulfilled and auth/loaduser/rejected in the first instance?


Solution

  • The loginUser action doesn't set any token value, state, or anything in localStorage. What does set the token value into state/localStorage is the loginUser.fulfilled action, specifically in the reducer function that handles it. This is a rather invalid use of a Redux reducer function though as reducer functions should be pure and free of side-effects, intentional or otherwise. What this means though is that while loginUser is still being processed and dispatches loadUser(res.data) the Redux state is yet to be updated. This means localStorage hasn't been updated either.

    My suggestion here would be to move the localStorage update into the Thunk where the asynchronous and side-effect logic is more appropriate.

    export const loginUser = createAsyncThunk(
      'auth/login',
      async ({ email, password }, { dispatch, rejectWithValue }) => {
        const body = { email, password };
      
        const config = {
          headers: {
            'Content-Type': 'application/json',
          },
        };
      
        try {
          const { data } = await axios.post(
            'http://localhost:5000/api/auth/login',
            body,
            config
          );
    
          localStorage.setItem('token', data.token);
      
          dispatch(loadUser(data));
          return data;
        } catch (err) {
          return rejectWithValue(err.response.data);
        }
      }
    );
    
    export const authSlice = createSlice({
      name: 'auth',
      initialState,
      reducers: {
        ...
      },
      extraReducers: (builder) => {
        builder
          ...
          .addCase(loginUser.fulfilled, (state, action) => {
            state.isAuthenticated = true;
            state.isLoading = false;
            state.user = action.payload;
            state.token = action.payload.token;
          })
          ...
      }
    });