Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on Redux Toolkit Issue with Managing States

Post

Redux Toolkit Issue with Managing States

+2
−0

I am all new with using Redux Toolkit, I followed the official documentation to setup the store:

// store.ts
import { configureStore, ThunkAction, Action, combineReducers } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import authReducer from '../features/authentication/redux/authSlice';
import delegationReducer from '../features/delegations/redux/delegationSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    auth: authReducer,
    delegations: delegationReducer
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

As you can see, I have three reducers: authReducer, delegationReducer and counterReducer which are not related to each other. The issue is when I try to dispatch an action to change the auth state (for example getAuthenticatedUser()) all the other reducers partially reset to initial state. The same goes for the other reducers.

I will provide you the code of authSlice.tsx and delegateSlice.tsx to demonstrate better the problem (I apologize if it is too long):

// authSlice.tsx
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios, { AxiosInstance } from "axios";
import { toast } from "react-toastify";

const authInstance: AxiosInstance = axios.create({
  baseURL: "http://localhost:4000/api/auth",
});

interface AuthState {
  token: any;
  isLoading: boolean | null;
  isAuthenticated: boolean | null;
  userData: any;
  error: any;
}

export const registerAdmin = createAsyncThunk(
  "auth/register",
  async (userData: any, { rejectWithValue }) => {
    try {
      const response = await authInstance.post("/register", userData, {
        headers: {
          "Content-Type": "application/json",
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const loginUser = createAsyncThunk(
  "auth/login",
  async (userData: any, { rejectWithValue }) => {
    try {
      const response = await authInstance.post("/", userData, {
        headers: {
          "Content-Type": "application/json",
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const getAuthenticatedUser = createAsyncThunk(
  "auth/get_authenticated_user",
  async (_, { rejectWithValue }) => {
    try {
      const response = await authInstance.get("/", {
        headers: {
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const requestPasswordChange = createAsyncThunk(
  "auth/request_password_change",
  async (userData: any, { rejectWithValue }) => {
    try {
      const response = await authInstance.post("/password", userData, {
        headers: {
          "Content-Type": "application/json",
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

const initialState = {
  token: localStorage.getItem("auth_token"),
  isLoading: false,
  isAuthenticated: localStorage.getItem("auth_token") !== null,
  error: null,
  userData: null,
} as AuthState;

const authPending = (state: AuthState) => {
  state.isLoading = true;
  state.error = null;
  state.userData = null;
  state.isAuthenticated = false;
};

const authRejected = (state: AuthState, action: any) => {
  localStorage.removeItem("auth_token");
  state.isLoading = false;
  state.error = action.payload;
  state.isAuthenticated = false;
};

export const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    logoutUser(state: AuthState) {
      localStorage.removeItem("auth_token");
      state.isLoading = false;
      state.error = null;
      state.isAuthenticated = false;
      state.userData = null;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(registerAdmin.pending, authPending)
      .addCase(loginUser.pending, authPending)
      .addCase(getAuthenticatedUser.pending, authPending)
      .addCase(requestPasswordChange.pending, authPending)
      .addCase(registerAdmin.rejected, authRejected)
      .addCase(loginUser.rejected, authRejected)
      .addCase(getAuthenticatedUser.rejected, authRejected)
      .addCase(requestPasswordChange.rejected, authRejected)
      .addCase(registerAdmin.fulfilled, (state: AuthState, { payload }) => {
        state.isLoading = false;
        state.isAuthenticated = true;
        state.userData = null;
        state.error = null;
        localStorage.setItem("auth_token", payload.token);
      })
      .addCase(loginUser.fulfilled, (state: AuthState, { payload }) => {
        state.isLoading = false;
        state.isAuthenticated = true;
        state.userData = null;
        state.error = null;
        localStorage.setItem("auth_token", payload.token);
      })
      .addCase(
        getAuthenticatedUser.fulfilled,
        (state: AuthState, { payload }) => {
          state.isLoading = false;
          state.isAuthenticated = true;
          state.userData = payload;
          state.error = null;
          state.token = localStorage.getItem("auth_token");
        }
      )
      .addCase(
        requestPasswordChange.fulfilled,
        (state: AuthState, { payload }) => {
          state.isLoading = false;
          state.isAuthenticated = false;
          state.userData = false;
          state.error = null;
          state.token = localStorage.getItem("auth_token");
        }
      )
      .addDefaultCase((state: AuthState) => {
        state.token = localStorage.getItem("auth_token");
        state.isLoading = false;
        state.isAuthenticated = localStorage.getItem("auth_token") ? true : false
        state.error = null;
        state.userData = null;
      });
  },
});

export const { logoutUser } = authSlice.actions;

export default authSlice.reducer;

// delegationSlice.tsx
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import axios, { AxiosInstance } from "axios";
import { toast } from "react-toastify";

const delegationsInstance: AxiosInstance = axios.create({
  baseURL: "http://localhost:4000/api/delegations",
});

interface DelegationsState {
  delegationList: any;
  delegateList: any;
  delegationData: any;
  delegateData: any;
  isLoading: boolean | null;
  error: any;
  storedDelegationData: any;
}

export const addDelegation = createAsyncThunk(
  "delegations/add_delegation",
  async (delegationData: any, { rejectWithValue }) => {
    try {
      const response = await delegationsInstance.post("/", delegationData, {
        headers: {
          "Content-Type": "application/json",
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const addDelegate = createAsyncThunk(
  "delegations/add_delegate",
  async (data: any, { rejectWithValue }) => {
    const { delegationId, delegateData } = data;
    try {
      const response = await delegationsInstance.post(
        `/delegates/${delegationId}`,
        delegateData,
        {
          headers: {
            "Content-Type": "application/json",
            Authorize: localStorage.getItem("auth_token"),
          },
        }
      );
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const getDelegationList = createAsyncThunk(
  "delegations/get_delegation_list",
  async (_, { rejectWithValue }) => {
    try {
      const response = await delegationsInstance.get("/delegations", {
        headers: {
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const getDelegation = createAsyncThunk(
  "delegations/get_delegation",
  async (data: any, { rejectWithValue }) => {
    const { id } = data;
    try {
      const response = await delegationsInstance.get(`/delegations/${id}`, {
        headers: {
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const getDelegateList = createAsyncThunk(
  "delegations/get_delegate_list",
  async (_, { rejectWithValue }) => {
    try {
      const response = await delegationsInstance.get("/delegates", {
        headers: {
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const getDelegate = createAsyncThunk(
  "delegations/get_delegate",
  async (data: any, { rejectWithValue }) => {
    const { id } = data;
    try {
      const response = await delegationsInstance.get(`/delegates/${id}`, {
        headers: {
          Authorize: localStorage.getItem("auth_token"),
        },
      });
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const updateDelegation = createAsyncThunk(
  "delegations/update_delegation",
  async (data: any, { rejectWithValue }) => {
    const { id, delegationData } = data;
    try {
      const response = await delegationsInstance.put(
        `/delegations/${id}`,
        delegationData,
        {
          headers: {
            "Content-Type": "application/json",
            Authorize: localStorage.getItem("auth_token"),
          },
        }
      );
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const updateDelegate = createAsyncThunk(
  "delegations/update_delegate",
  async (data: any, { rejectWithValue }) => {
    const { id, delegateData } = data;
    try {
      const response = await delegationsInstance.put(
        `/delegates/${id}`,
        delegateData,
        {
          headers: {
            "Content-Type": "application/json",
            Authorize: localStorage.getItem("auth_token"),
          },
        }
      );
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const removeDelegation = createAsyncThunk(
  "delegations/remove_delegation",
  async (data: any, { rejectWithValue }) => {
    const { id } = data;
    try {
      const response = await delegationsInstance.put(
        `/delegations/remove/${id}`,
        {
          headers: {
            Authorize: localStorage.getItem("auth_token"),
          },
        }
      );
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

export const removeDelegate = createAsyncThunk(
  "delegations/remove_delegate",
  async (data: any, { rejectWithValue }) => {
    const { id } = data;
    try {
      const response = await delegationsInstance.put(
        `/delegates/remove/${id}`,
        {
          headers: {
            Authorize: localStorage.getItem("auth_token"),
          },
        }
      );
      return response.data;
    } catch (error: any) {
      const errorMessages = error?.response?.data?.errors?.map(
        (err: any) => err.msg
      );
      toast.error(errorMessages?.join("\n"));
      return rejectWithValue(error.response.data);
    }
  }
);

const initialState = {
  delegationList: [],
  delegateList: [],
  delegationData: null,
  delegateData: null,
  isLoading: false,
  error: null,
  storedDelegationData: null,
} as DelegationsState;

const delegPending = (state: DelegationsState) => {
  state.isLoading = true;
  state.error = null;
};

const delegRejected = (state: DelegationsState, action: any) => {
  state.isLoading = false;
  state.error = action.payload;
};

export const delegationSlice = createSlice({
  name: "delegations",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(addDelegation.pending, delegPending)
      .addCase(addDelegate.pending, delegPending)
      .addCase(getDelegationList.pending, delegPending)
      .addCase(getDelegateList.pending, delegPending)
      .addCase(getDelegation.pending, delegPending)
      .addCase(getDelegate.pending, delegPending)
      .addCase(updateDelegation.pending, delegPending)
      .addCase(updateDelegate.pending, delegPending)
      .addCase(removeDelegation.pending, delegPending)
      .addCase(removeDelegate.pending, delegPending)
      .addCase(addDelegation.rejected, delegRejected)
      .addCase(addDelegate.rejected, delegRejected)
      .addCase(getDelegationList.rejected, delegRejected)
      .addCase(getDelegateList.rejected, delegRejected)
      .addCase(getDelegation.rejected, delegRejected)
      .addCase(getDelegate.rejected, delegRejected)
      .addCase(updateDelegation.rejected, delegRejected)
      .addCase(updateDelegate.rejected, delegRejected)
      .addCase(removeDelegation.rejected, delegRejected)
      .addCase(removeDelegate.rejected, delegRejected)
      .addCase(
        addDelegation.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [...state.delegationList, payload];
          state.delegateList = [];
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        addDelegate.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = [...state.delegateList, payload];
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        getDelegationList.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = payload;
        }
      )
      .addCase(
        getDelegateList.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = payload;
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        getDelegation.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = [];
          state.delegationData = payload;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        getDelegate.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = [];
          state.delegationData = null;
          state.delegateData = payload;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        updateDelegation.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = state.delegationList.map((el: any) =>
            el._id === payload._id ? payload : el
          );
          state.delegateList = [];
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        removeDelegation.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = state.delegationList.map((el: any) =>
            el._id === payload._id ? payload : el
          );
          state.delegateList = [];
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        updateDelegate.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = state.delegateList.map((el: any) =>
            el._id === payload._id ? payload : el
          );
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addCase(
        removeDelegate.fulfilled,
        (state: DelegationsState, { payload }) => {
          state.isLoading = false;
          state.delegationList = [];
          state.delegateList = state.delegateList.map((el: any) =>
            el._id === payload._id ? payload : el
          );
          state.delegationData = null;
          state.delegateData = null;
          state.error = null;
          state.storedDelegationData = null;
        }
      )
      .addDefaultCase((state: DelegationsState) => {
        state.isLoading = false;
        state.delegationList = [];
        state.delegateList = [];
        state.delegationData = null;
        state.delegateData = null;
        state.error = null;
        state.storedDelegationData = null;
      });
  },
});

export const {} = delegationSlice.actions;

export default delegationSlice.reducer;

As you can see, the two slices are independent of each other, when I try to dispatch getDelegationList(), I get successfully the list of delegations but userData becomes null (isAuthenticated still true and there is a token in the local storage). when I try to dispatch getAuthenticatedUser(), I get successfully the user data but the delegation list becomes [].

What causes this problem and how to fix it ?

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

2 comment threads

localStorage is typically a "side-effect" (1 comment)
Possible (im)mutability issue? (1 comment)
localStorage is typically a "side-effect"
Alexei‭ wrote over 1 year ago

localStorage is typically handled similarly to other "side effects" (e.g. HTTP calls) and the operations related to it are handled outside of the reducer functions (e.g. middlwares). createListenerMiddleware seems up to do this.