State value not change when dispatch to another reduces in redux - reactjs

I am building login system. I am having problem with store state value when I use Selector function. in my system, I want to get isSuccess, error, userInfo from store state values
/login/index.js
const userLogin = useSelector((state) => state.userLogin);
console.log(userLogin);
const { isSuccess, error, userInfo } = userLogin;
if error it will show error message on UI, otherwise if isSuccess it will show success message. isSuccess appears when post to api success (it mean user login success) and user data is dispatched to loginSuccess reduces else error value dispatched to loginFail reduces
/action/userAction.js
import {
loginSuccess,
loginFail,
} from "\~/redux/user/userLoginSlice";
export const login = (inputs) =\> async (dispatch) =\> {
try {
const {data} = await request.post("/auth/login", inputs);
dispatch(loginSuccess(data));
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error.response.data)
dispatch(loginFail(error.response.data))
}
. however instead of just getting isSuccess in loginSucces reduce, it also get error when previous login was loginFail
console.log(userLogin). it should only show userInfo and isSuccess. anyone help me to solve this problem with . this is reduces
userLoginSlice.js
import { createSlice } from '#reduxjs/toolkit';
const userLoginSlice = createSlice({
name: 'userInfo',
initialState: {},
reducers: {
loginSuccess: (state, action) => {
state.isSuccess = true;
state.userInfo = action.payload;
},
loginFail: (state, action) => {
state.error = action.payload
},
},
});
export const { loginSuccess, loginFail } = userLoginSlice.actions;
export default userLoginSlice.reducer;
and my reduce store
store.js
import { configureStore } from '#reduxjs/toolkit';
import productModalReducer from './product-modal/productModalSlice';
import cartItemsSlice from './cart/cartSlide';
import userLoginSlice from './user/userLoginSlice.js';
import userRegisterSlice from './user/userRegisterSlice.js';
import userUpdateSlice from './user/userUpdateSlice.js';
const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
export const store = configureStore({
reducer: {
productModal: productModalReducer,
cartItems: cartItemsSlice,
userLogin: userLoginSlice,
userRegister: userRegisterSlice,
userUpdate: userUpdateSlice,
},
preloadedState: initialState,
});
State value not change when dispatch to another reduces in redux

It seems that this could be because loginSuccess is only adding userInfo and isSuccess to the state but not omitting previous error.
Perhaps try reset state value to exclude error in the reducer:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
state = { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
state = { error: action.payload };
},
},
});
Or return a state value instead of using Immer proxy state:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
return { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
return { error: action.payload };
},
},
});

Related

Redux-Toolkit dispatch in useEffect hook goes to an infinite loop

problem
Hello. sir/madam. I'm having this issue and driving me crazy now. It might be appreciated if you can help me to find a solution for this.
I use useMuation from react-query to do post requests and get the user info from JSON and then try to store it to my redux store using useEffect according to the status given by react-query useMutation hook which is success. The problem rises in this status. all info is successfully stored in the redux store as you can see in the picture, but it causes infinite loop.
I tried to put an empty dependency array and even put userData?.data?.data?.user?.name and userData?.data?.token instead of userData but still the same.
Could anybody can help me out..?
Error
Store
userSlice.ts
import { createSlice, configureStore, PayloadAction } from "#reduxjs/toolkit";
type initialState = {
user: string;
dashboardIndex: number;
theme: string;
token: string;
isLoggedIn: boolean;
};
const initialState: initialState = {
user: "",
dashboardIndex: 0,
theme: "themeLight",
token: "",
isLoggedIn: false,
};
const userSlice = createSlice({
name: "user",
initialState: initialState,
reducers: {
updateUser(state, action: PayloadAction<string>) {
state.user = action.payload;
},
updateDashboardIndex(state, action: PayloadAction<number>) {
state.dashboardIndex = action.payload;
},
updateTheme(state, action: PayloadAction<string>) {
state.theme = action.payload;
},
updateToken(state, action: PayloadAction<string>) {
state.token = action.payload;
},
updateIsLoggedIn(state, action: PayloadAction<boolean>) {
state.isLoggedIn = action.payload;
},
reset: () => initialState,
},
});
...
Login.tsx
const LoginComponents = () => {
let navigate = useNavigate();
const [loginObject, setLoginOject] = useState<loginObjectType>({
email: "",
password: "",
});
const {
mutate,
error,
isError,
isSuccess,
data: userData,
} = useQueryMutationInvalidateHooksPost("api/v1/users/login");
const dispatch = useAppDispatch();
...
// Signin process handler
useEffect(() => {
console.log("hi");
if (isSuccess) {
if (userData) {
dispatch(usersActions.updateUser(userData?.data?.data?.user?.name));
dispatch(usersActions.updateToken(userData?.data?.token));
dispatch(usersActions.updateIsLoggedIn(!!userData?.data?.token));
alert(
`Succeeded in login. Welcome ${userData?.data?.data?.user?.name}!`
);
navigate("/home");
}
}
if (isError) {
if (error instanceof AxiosError) {
alert(error?.response?.data?.message);
}
}
}, [navigate, error, isSuccess, isError, userData, dispatch]);
// Button functions
const submitHandler = async (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.preventDefault();
if (!loginObject?.email || !loginObject?.password) {
alert("Please input all required fields.");
} else if (loginObject?.email && loginObject.password) {
// fetching data
const temp = {
...loginObject,
};
mutate(temp);
}
};
I tried to put an empty dependency array and even put userData?.data?.data?.user?.name and userData?.data?.token instead of userData but still the same.
Could anybody can help me out..?

Data fetched from server not loading into redux store?

I'm fetching data from a mongoDB database and then fetch that data from the server and finally render the data to the UI in a specified component. I'm using redux-toolkit for state management.
The problem is when fetching the data from the server it is not visible in the store. Why is the empty array in the initial state still empty after fetching the data? I'm using createSlice Api that generates the action creators and action types and createAsyncThunk Api for the asynchronous task of fetching the data from the server.
Slice reducer
import { createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import axios from 'axios'
const initialState = {
realestate: [],
isSuccess: false,
isLoading: false,
message: '',
}
export const getRealEstates = createAsyncThunk(
'realestate/getRealEstates', async () => {
try {
const response = await axios.get('castles')
return response.data
} catch (error) {
console.log(error)
}
}
)
export const estateSlice = createSlice({
name: 'realestate',
initialState,
reducers: {
reset: (state) => initialState,
},
extrareducers: (builder) => {
builder.addCase(getRealEstates.pending, (state) => {
state.isLoading = true
})
builder.addCase(getRealEstates.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.realestate = action.payload
})
builder.addCase(getRealEstates.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const { reset } = estateSlice.actions
export default estateSlice.reducer
Store
export const store = configureStore({
reducer: {
realestate: realestateReducer,
registered: registerReducer,
},
});
Component
const realestates = useSelector(state => state.realestate)
const { isLoading, realestate, isError, message, isSuccess} = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(getRealEstates())
if(realestate){
setShow(true)
}else{
console.log('No data retrieved')
}
}, [dispatch, isError, realestate, message])
It's extraReducers with an uppercase R, your code contains extrareducers.

rtk createAsyncThunk doesnt update the state

I am trying to auth session by random user with http get request and createAsyncThunk.
fetching the user data in App.js on mount hook.
I can see the get request in my network and the new fetched state in redux dev tool,
but my TopBar.js useSelector return the initial state before the fetch.
TopBar.js user log:
App.js:
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRandomUserData())
}, [dispatch]);
authSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
isLoggedIn: true,
user: {},
loading: false,
error: null,
};
export const fetchRandomUserData = createAsyncThunk(
'auth/fetchRandomUser',
async () => {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
return data.results[0];
} catch (error) {
throw Error(error);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout(state, action) {
state.isLoggedIn = false;
},
},
extraReducers: {
[fetchRandomUserData.pending]: (state, action) => {
state.loading = true;
state.error = null;
},
[fetchRandomUserData.fulfilled]: (state, action) => {
console.log("action.payload",action.payload);
state.user = action.payload;
state.loading = false;
},
[fetchRandomUserData.rejected]: (state, action) => {
state.error = action.error.message;
state.loading = false;
},
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
// import booksReducer from './reducers/booksReducer';
import booksReducer from './slices/bookSlice';
import authReducer from './slices/authSlice';
const store = configureStore({
reducer: { books: booksReducer, auth: authReducer },
});
export default store;
TopBat.js:
export default function TopBar(): JSX.Element {
const user = useSelector((state: any) => state.auth);
console.log("topbar",user); // returns the initial state
//....
Please make sure that you update react-redux to version 8 if you are using react 18.
There are known cases where components just stop updating if you are using react-redux 7 and lower with react 18.

How can I change other reducer state from pending/rejected case in createAsyncThunk

so I have auth reducer and loading reducer. I'd like to set the state in loading reducer whenever the createAsynchThunk in auth reducer is pending. the code look like this:
//auth reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import {callPOSTSignInUserAccount} from "api"
export const signInRequest = createAsyncThunk(
"auth/login",
async (userData: UserDataLogin, thunkAPI) => {
try {
const result = await callPOSTSignInUserAccount(
userData.email,
userData.password
);
const auth = result.data.AuthenticationResult;
const user = result.data.user;
catch(err) {
const result = {
alert: {
type: "error",
message: errMsg
}
}
return thunkAPI.rejectWithValue(result)
}
}
//state
const authState = {
isAuthenticated = true,
errorSignIn = "",
auth: {},
};
//slice for auth
const sliceAuth = createSlice({
name: "auth",
initialState: authState,
reducers: {},
extraReducers: (builder) => {
//Sign in request
.addCase(signInRequest.pending, (state, action) => {
//set loading reducer state from here
})
.addCase(signInRequest.fulfilled, (state, action:any) => {
if (action.payload?.auth !== undefined) {
state.isAuthenticated = true
state.errorSignIn = ""
state.auth = action.payload.auth
}
})
.addCase(signInRequest.rejected, (state, action:any) => {
//also set alert here
})
}
const authReducer = sliceAuth.reducer
export default authReducer
//loading reducer
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const loadingState = {
appLoading: false,
};
const sliceLoading = createSlice({
name: "loading",
initialState: loadingState,
reducers: {
setLoading: (state, action) => {
state.apploading = action.payload
}
})
const reducerLoading = sliceLoading.reducer
export default reducerLoading
from what I read I can't dispatch an action in reducer because it's anti-pattern. I want to change the loading state in loading reducer from the auth reducer.
I can add loading in the auth reducer initial state but it become hard to manage whenever I have more than one reducer in a react component.

Common loading state reducer with Redux toolkit

I'm working on an app where I have multiple slices. I'm using createAsyncThunk for API calls and I like it cause it provides action creators for different state of API request, so that I can track loading state and errors within the reducer. But my question is, what if I want to have a separate reducer to track loading, error and success of my API calls how do I accomplish that with redux-toolkit
I know I can dispatch an action from within my createAsyncThunk function but it doesn't feel right and kinda defeats the purpose of the function itself. Also, side effects inside the reducer are considered to be a bad practice. So, I'm kinda confused at this point, I want to have just one Loader component in the root of the app that gets triggered when the loading state is true and it doesn't matter what exactly is loading
Here is an example of my current code:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit'
import { AxiosError } from 'axios'
import { masterInstance } from 'api'
import { GetAccessCodeParams, RegistrationStateType } from 'store/slices/registration/types'
export const getAccessCodeRequest = createAsyncThunk<void, GetAccessCodeParams, { rejectValue: { message: string } }>(
'registration/getAccessCodeRequest',
async ({ email }, { rejectWithValue }) => {
try {
await masterInstance.post(`/authorization/getAccessCodeWc`, { email })
} catch (err) {
let error: AxiosError = err
if (error) {
return rejectWithValue({
message: `Error. Error code ${error.response?.status}`,
})
}
throw err
}
}
)
const initialState: RegistrationStateType = {
isLoading: false,
error: null,
}
const registrationSlice = createSlice({
name: 'registration',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getAccessCodeRequest.fulfilled, (state) => {
state.isLoading = false
state.error = null
})
builder.addCase(getAccessCodeRequest.pending, (state) => {
state.isLoading = true
state.error = null
})
builder.addCase(getAccessCodeRequest.rejected, (state, action) => {
if (action.payload) {
state.error = {
message: action.payload.message,
}
} else {
state.error = action.error
}
state.isLoading = false
})
},
})
export const registrationReducer = registrationSlice.reducer
I want isLoading and error to be in a separate reducer
You could have a shared reducer matcher function.
// mySharedStuff.js
export const handleLoading = (action, (state) => {
state.loading = action.type.endsWith('/pending'); // or smth similar
});
export const handleError = (action, (state) => {
state.error = action.type.endsWith('/rejected'); // or smth similar
});
// mySlice.js
const mySlice = createSlice({
name: 'FOO',
initialState: {},
reducers: {},
extraReducers: builder => {
builder.addMatcher(handleLoading),
builder.addMatcher(handleError),
...

Resources