cannot update initalState in redux-toolkit with createAsyncThunk - reactjs

Im trying to set and update initialState in redux toolkit after fetch operation
pageSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { deletePage, getPages, savePage, updatePage } from "../../services/page.service";
import { initialPage } from "../../components";
export const getUserPages = createAsyncThunk(
"pages/getUserPages",
async() => {
const pages = await getPages();
return pages
}
)
export const saveUserPage = createAsyncThunk(
"pages/saveUserPage",
async(page) => {
const savedPage = await savePage(page);
return savedPage;
}
)
export const pageSlice = createSlice({
name: "pages",
initialState: {
pageStatus: "idle",
pages: []
},
reducers: {},
extraReducers: {
[getUserPages.pending]: (state) => {
state.pageStatus = "loading";
},
[getUserPages.fulfilled]: (state, action) => {
state.pages = action.payload
state.pageStatus = "pageLoaded"
},
[getUserPages.rejected]: (state) => {
state.pageStatus = "error"
},
[saveUserPage.pending]: (state) => {
state.pageStatus = "loading";
},
[saveUserPage.fulfilled]: (state, action) => {
state.pages.push(action.payload)
state.pageStatus = "pageLoaded"
},
[saveUserPage.rejected]: (state) => {
state.pageStatus = "error"
}
}
})
export default pageSlice.reducer;
initialState: {
pageStatus: "idle",
pages: []
},
Working on note app with redux-toolkit. pages array will contain array of objects.
In extraReducers
[saveUserPage.fulfilled]: (state, action) => {
// console.log(state.pages) if empty( undefined ) else [{element}]
state.pages.push(action.payload)
state.pageStatus = "pageLoaded"
}
if initailState pages array contain any single element [saveUserPage.fulfilled] work fine.
but if array is empty then i get error
Cannot read property 'push' of undefined at pages/saveUserPage/fulfilled
if console.log(state.pages) in s
What I'm doing wrong ?

Based on the Error message you have mentioned Cannot read property 'push' of undefined at pages/saveUserPage/fulfilled, state.pages is not an empty array, It is undefined. That is the reason you are seeing this error.
Array.push operation, all it expects the variable to be array i.e. [], It doesn't matter whether it is empty or it has items.
Please check the below code block or some other operation, whether it is assigning it as 'undefined' in the first place.
[getUserPages.fulfilled]: (state, action) => {
state.pages = action.payload
state.pageStatus = "pageLoaded"
},
Workaround solution:
[saveUserPage.fulfilled]: (state, action) => {
if(state.pages === undefined){
state.pages = [];
if(action.payload && action.payload.length > 0){ / Make sure that payload is an array
state.pages = action.payload; //
}
}
else{
state.pages.push(action.payload);
}
state.pageStatus = "pageLoaded"
},

Related

useSelector is not giving the value after useDispatch

I have an api which gives me the result, and I can see the data in my console, but I'm not able to get it in useSelector.
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import axios from "axios";
import { useNavigate } from "react-router-dom";
const initialState = {
value: [],
status: 'idle',
};
export const fetchEmployeesThunk = createAsyncThunk(
'employeelist/fetchEmployeesThunk',
async () => {
const res = await axios.get('https://localhost:7168/Employee/GetEmployeeList').then(
(result) => result.data
)
return res;
})
export const EmployeeListSlice = createSlice({
name: "employeelist",
initialState: initialState,
reducers: {
initialFetch: (state, action) => {
state.value = action.payload;
},
updateEmployeeList: (state, action) => {
state.value = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchEmployeesThunk.pending, (state, action) => {
state.status = 'idle';
state.value = [];
})
.addCase(fetchEmployeesThunk.fulfilled, (state, action) => {
console.log(action.payload);
state.value = action.payload;
state.status = 'finished';
})
},
});
export const getEmployeeListData = (state) => state.employeelist.value;
export const { updateEmployeeList, initialFetch } = EmployeeListSlice.actions;
export default EmployeeListSlice.reducer;
export function fetchEmployees() {
return async (dispatch) => {
const res = await axios.get('https://localhost:7168/Employee/GetEmployeeList').then(
(result) => result.data
)
dispatch(updateEmployeeList(res));
}
}
as you can see I tried using both thunk and creating a function and dispatching the data internally to an action, i was able to update the state but i'm not able to get the value through selector, I have a table which takes an array
export default function HomePage() {
const dispatch = useDispatch();
const [tempRows, setTempRows] = useState(useSelector((state) => state.employeelist.value));
const [rows, setTableRows] = useState(useSelector((state) => state.employeelist.value));
useEffect(() => {
//dispatch(fetchEmployees());
dispatch(fetchEmployeesThunk());
}, rows);
}
This is giving me empty array, but lets say if I change something then reload like a hot reload it returns the data now, any help would be deeply appreciated
Please do
const rows = useSelector((state) => state.employeelist.value)
and not
const [rows, setTableRows] = useState(useSelector((state) => state.employeelist.value));
The latter means "use local state that is once initialized from the Redux store". It will only change if setTableRows is called, not if the Redux store changes.

Trying to pass action.payload from one slice to another different slice

What I am trying to achieve is sending action payload from one slice to another and I have been stuck several hours trying to do so.
I have tried accessing the global store but the problem is I am getting errors on doing so
I am using redux-tool-kit to manage the state of my react application and I am trying to pass a payload from one slice to another, the following is my first slice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from 'axios';
import { clearAlert, displayIncorrectEmail } from "./features.js/Alert";
const initialState = {
user: user ? JSON.parse(user) : null,
isMember: false,
isLoading: true
}
This section still for the first slice
export const getRegisteredUser = createAsyncThunk('auth/getRegistrationRes', async (currentUser, thunkAPI) => {
try {
const response = await axios.post('/api/v1/auth/register', currentUser)
return response.data
} catch (error) {
// console.log(error.message)
thunkAPI.rejectWithValue(error.message)
}
})
export const getLoginUser = createAsyncThunk('auth/getLoginRes', async (currentUser, thunkAPI) => {
try {
const response = await axios.post('/api/v1/auth/login', currentUser)
thunkAPI.dispatch(displaySuccess())
setTimeout(() => {
thunkAPI.dispatch(clearAlert())
}, 3000);
return response.data
} catch (error) {
thunkAPI.dispatch(displayIncorrectEmail())
// console.log(error.response.data.msg);
thunkAPI.rejectWithValue(error.message)
//the below return is the action-payload I want to pass to another slice
return error.response.data.message
//
}
})
const authenticationSlice = createSlice({
name: 'auth',
initialState,
reducers: {
},
extraReducers: {
// login user reducers
[getLoginUser.pending]: (state) => {
state.isLoading = true;
},
[getLoginUser.fulfilled]: (state, action) => {
state.isLoading = false;
// console.log(action.payload.getState());
// action.payload.load = true
state.user = action.payload.user
},
[getLoginUser.rejected]: (state) => {
state.isLoading = false;
state.user = null
},
}
})
export const { registerUser, loginUser } = authenticationSlice.actions
export default authenticationSlice.reducer
This is the second slice is the code below
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
showAlert: false,
alertText: '',
alertType: '',
}
const alertSlice = createSlice({
name: 'alert',
initialState,
reducers: {
displayIncorrectEmail: (state, action) => {
state.showAlert = !state.showAlert
//I want to pass the action.payload to this instead of hard-coding it to 'incorrect email' //below
state.alertText = 'incorrect email'
//the state.alertText above
state.alertType = "danger"
},
clearAlert: (state) => {
// setTimeout(() => {
state.showAlert = !state.showAlert;
// }, 4000);
}
}
})
export const { displayDanger, clearAlert, displaySuccess, displayIncorrectEmail } = alertSlice.actions
export default alertSlice.reducer
Kindly help if you have an idea on how to sort this.
cheers.
Just add an extraReducer for getLoginUser.rejected to the second slice as well. You can add that to the extraReducers of as many slices as you want to.
By the way, you really should not be the map object notation of extraReducers, but the extraReducers "builder callback" notation. The object notation you are using is soon going to be deprecated and we have been recommending against it in the docs for a long time.

Can I change state from one slice to another slice in Redux?

I have 2 slices, the first of which contains state errors and the second of which contains logic.
Is it possible to change the value state in the error slice from a logical slice?
Error slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
error: false,
};
export const errorSlice = createSlice({
name: "error",
initialState,
reducers: {
setError: (state, action) => {
state.error = action.payload;
},
},
});
export const { setError } = errorSlice.actions;
export default errorSlice.reducer;
Logical slice
import { createSlice } from "#reduxjs/toolkit";
export const doSomething = (data) => {
return (dispatch) => {
dispatch(setData(data.text))
// here I want dispatch setError from errorSlice
// dispatch(setError(data.error))
};
};
const initialState = {
data: null,
};
export const logicalSlice = createSlice({
name: "logical",
initialState,
reducers: {
setData: (state, action) => {
state.error = action.payload;
},
},
});
export const { setData } = logicalSlice.actions;
export default logicalSlice.reducer;
And I need to run it from a component with a single dispatch
dispatch(doSomething(data))
Is there such a possibility?
Thank you!

How can I change state of another reducer from extra reducers add cases

I am trying to create a notification Component.
My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.
The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.
User Slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialUserState = {
currentUser:null
}
export const getUser = createAsyncThunk(
'user/getUser',
async (endpoint, data) => {
return(
await axios.post(endpoint, data)
.then(res =>{
return res.data.user
})
.catch(error =>{
throw Error(error.response.data)
})
)
}
)
const userSlice = createSlice({
name: 'user',
initialState: initialUserState,
reducers:{
currentUser(state, action){
state.currentUser = action.payload
}
},
extraReducers:
(builder) => {
builder.addCase(getUser.pending, ()=>{
console.log("authing")
})
builder.addCase(getUser.fulfilled, (state, action)=>{
state.currentUser = action.payload
console.log("fulfilled")
})
builder.addCase(getUser.rejected, (state, action)=>{
console.log("failed")
alert(action.error.message)
})
}
})
export const userActions = userSlice.actions;
export default userSlice.reducer;
notificationSlice
import React from 'react'
import { useSelector } from 'react-redux'
function Notification() {
const toast = useSelector(state => state.notification)
console.log(toast)
return (
toast.active &&
<div className="notification" style={{backgroundColor:toast.backgroundColor}} >
{toast.message}
</div>
)
}
export default Notification
I want to change notification state when ever one of the extra reducer in userSlice is called
I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.
I have done something like the following, which I think would work well for you:
import { createEntityAdapter, createSlice, isAnyOf } from '#reduxjs/toolkit'
const notificationsAdapter = createEntityAdapter()
const initialState = notificationsAdapter.getInitialState({
error: null,
success: null,
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
clearNotifications: state => {
state.error = null
state.success = null
},
setError: (state, action) => {
state.success = null
state.error = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload
state.error = null
},
},
extraReducers: builder => {
builder
.addMatcher(
isAnyOf(
getUser.fulfilled,
),
(state, action) => {
state.error = null
state.success = action.payload.message
}
)
.addMatcher(
isAnyOf(
getUser.rejected
// can add as many imported actions
// as you like to these
),
(state, action) => {
state.error = action?.payload
state.success = null
}
)
// reset all messages on pending
.addMatcher(
isAnyOf(
getUser.pending
),
(state, action) => {
state.error = null
state.success = null
}
)
},
})
export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
export default notificationsSlice.reducer
export const getErrorMsg = state => state.notifications.error
export const getSuccessMsg = state => state.notifications.success
Having added the above, you can now create a notification component that listens for
const error = useSelector(getErrorMsg)
const success = useSelector(getSuccessMsg)
and shows the messages accordingly.
Caveat:
My notificationSlice code assumes that when an action completes, there will exist a "message" object on the success payload. So, on my async thunks, if my api does not return this I must add this explicitly to the result.

Add to cart in redux-toolkit (state undefined), Someone, please help me?

I am trying to add a product to the cart.
I don't understand why the state is undefined,
I have a localStorage 'cart' :
(3) [{…}, {…}, {…}]
0: {product: {…}, quantity: 1}
1: {product: {…}, quantity: 2}
console.log(action.payload):
{product: {…}, quantity: 1}
When I clicked the add cart button, my localStorage added correctly but the state was automatically lost
My code cartSlice.js:
import { createSlice } from '#reduxjs/toolkit';
import {
createCart,
getCart,
updateCart,
deleteCart
} from './../asyncActions/cart.asyncAction';
var data = JSON.parse(localStorage.getItem('cart'));
const cartSlice = createSlice({
name: 'cart',
initialState: {
cart: data ? data : [],
searchValue: '',
},
reducers: {
},
extraReducers: {
//* get cart
[getCart.pending]: (state, action) => {
},
[getCart.fulfilled]: (state, action) => {
if (action.payload) {
state.cart = action.payload;
}
},
[getCart.rejected]: (state, action) => {
},
// create
[createCart.pending]: (state, action) => {
},
[createCart.fulfilled]: (state, action) => {
if (action.payload) {
let idProductAction = action.payload.product.id;
var index = state.cart ? state.cart.map((item) => item.product.id).indexOf(idProductAction) : -1;
if(index !== -1){
state.cart[index].quantity += action.payload.quantity;
} else {
state.cart = [action.payload, ...state.cart];
}
state.cart = localStorage.setItem('cart', JSON.stringify(state.cart));
}
},
[createCart.rejected]: (state, action) => {
console.log('sai');
},
}
});
const { actions, reducer } = cartSlice;
const { clearStateCart } = actions;
export { clearStateCart };
export default reducer;
My code component Cart.js:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getCart, createCart } from '../../store/asyncActions/cart.asyncAction';
function Cart(props) {
const classes = useStyles();
const dispatch = useDispatch();
let cart = useSelector((state) => state.cart.cart);
let productList = useSelector((state) => state.products.products);
const addCart = (product) => {
getDispatchCreateCart(product);
}
const getDispatchCreateCart = (product) => {
dispatch (
createCart({
product: product,
quantity: 1
})
)
}
const getDispatchProducts = () => {
dispatch (
getProducts()
)
}
const getDispatchGetCart = () => {
dispatch (
getCart()
)
}
useEffect(() => {
getDispatchProducts();
getDispatchGetCart();
}, []);
return (...);
}
export default Cart;
my redux devtools show:
cart/createCart/pedding : state cart has data.
enter image description here
cart/createCart/fulfilled : state cart undefined.
enter image description here
I don't understand why the state is undefined,
Hope everybody help please.
You can not state.cart, It will be lost by localStorage typeof undefined:
state.cart = localStorage.setItem('cart', JSON.stringify(state.cart));
Edit:
[createCart.fulfilled]: (state, action) => {
if (action.payload) {
let idProductAction = action.payload.product.id;
var index = state.cart ? state.cart.map((item) => item.product.id).indexOf(idProductAction) : -1;
if(index !== -1){
state.cart[index].quantity += action.payload.quantity;
} else {
state.cart = [action.payload, ...state.cart];
}
localStorage.setItem('cart', JSON.stringify(state.cart));
}
},

Resources