1

For the first time, I am using Redux in my React project. The code here I have added is for cookie-based authentication. I am worried that everything is here is in the correct format. It seems lots of duplicate code here. Especially for pending and rejected status in createSlice portion. How can I refactor this code and what will be the correct coding style in this case?

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

import API from "../API";

// Register user:

export const signup = createAsyncThunk(
  "user/signup",
  async (userInfo, { rejectWithValue }) => {
    try {
      const { data } = await API.post("/signup", userInfo);
      return data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

// Login:
export const login = createAsyncThunk(
  "user/login",
  async (loginInfo, { rejectWithValue }) => {
    try {
      const { data } = await API.post("/login", loginInfo);
      return data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

// Logout:
export const logout = createAsyncThunk(
  "user/logout",
  async (args, { rejectWithValue }) => {
    try {
      const { data } = await API.get("/logout");
      return data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

// Chek-Auth:
export const isAuthenticated = createAsyncThunk(
  "user/isAuthenticated",
  async (args, { rejectWithValue }) => {
    try {
      const { data } = await API.get("/check-auth");
      return data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

// createSlice portion is here:
export const userSlice = createSlice({
  name: "user",
  initialState: {
    loading: true,
    isLoggedIn: false,
    message: "",
    user: null,
    error: null,
  },
  reducers: {},
  extraReducers: {
    [signup.pending]: (state, action) => {
      state.loading = true;
    },
    [signup.fulfilled]: (state, action) => {
      state.loading = false;
      state.isLoggedIn = true;
      state.message = action.payload.message;
      state.user = action.payload.user;
      state.error = null;
    },
    [signup.rejected]: (state, action) => {
      state.loading = false;
      state.error = action.payload || action.error;
    },
    [login.pending]: (state, action) => {
      state.loading = true;
    },
    [login.fulfilled]: (state, action) => {
      state.loading = false;
      state.isLoggedIn = true;
      state.message = action.payload.message;
      state.user = action.payload.user;
      state.error = null;
    },
    [login.rejected]: (state, action) => {
      state.loading = false;
      state.error = action.payload || action.error;
    },
    [logout.pending]: (state, action) => {
      state.loading = true;
    },
    [logout.fulfilled]: (state, action) => {
      state.loading = false;
      state.isLoggedIn = false;
      state.message = action.payload.message;
      state.user = null;
    },
    [logout.rejected]: (state, action) => {
      state.loading = false;
      state.error = action.payload || action.error;
    },
    [isAuthenticated.pending]: (state, action) => {
      state.loading = true;
    },
    [isAuthenticated.fulfilled]: (state, action) => {
      state.loading = false;
      state.isLoggedIn = true;
      state.message = action.payload.message;
      state.user = action.payload.user;
    },
    [isAuthenticated.rejected]: (state, action) => {
      state.loading = false;
      state.error = action.payload || action.error;
    },
  },
});

// export const {  } = userSlice.actions;

export default userSlice.reducer;

1 Answers1

3

We generally recommend to use the builder notation, not the object notation. That makes stuff like this easier:

extraReducers: builder => {
  for (const thunk in [signup, login, logout, isAuthenticated]) {
    builder.addCase(thunk.pending, (state) => { state.loading = true })
    builder.addCase(thunk.rejected, (state, action) => { 
      state.loading = false;
      state.error = action.payload || action.error;
    })
  }
}

Keep in mind though that putting many asynchronous actions in the same state like you do here, sharing a loading state, may lead to race conditions.

Generally, for api cache stuff you should take a look into Redux Toolkit's Api cache abstraction, RTK-Query: https://redux-toolkit.js.org/rtk-query/overview

phry
  • 35,762
  • 5
  • 67
  • 81
  • Your answer is helpful, but it would be great if you would show the code for thunk.fulfilled as well. We need to use the same state for Asynchronous crud operation. Do you recommend using this approach in that case or any other alternative way? – shohag khan Jun 30 '21 at 05:15
  • 1
    I have shown you an example how to reduce common code, it might very well be necessary that you still write the rest by hand, since it differs. But generally, I would use a different loading variable for each ongoing request (even if it abstracting the code might get more difficult from it). And, again: I would probably not write any of this code, but use RTK-Query for it. – phry Jun 30 '21 at 07:10