Best way to share state between slices in redux toolkit? - reactjs

If I have a slice like this
cartSlice.js
import { createSlice } from "#reduxjs/toolkit";
import cartItems from "../cartItems";
const initialState = {
cartItems: cartItems,
amount: 1,
total: 0,
isLoading: true,
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
clearCart: (store) => {
store.cartItems = [];
},
removeItem: (store, action) => {
store.cartItems = store.cartItems.filter(
(item) => item.id !== action.payload
);
},
increase: (store, action) => {
const target_item = store.cartItems.find(
(item) => item.id === action.payload.id
);
target_item.amount++;
},
decrease: (store, { payload }) => {
const target_item = store.cartItems.find(
(item) => item.id === payload.id
);
target_item.amount--;
},
calculate: (store) => {
let amount = 0;
let total = 0;
store.cartItems.forEach((item) => {
amount += item.amount;
total += item.amount * item.price;
});
store.amount = amount;
store.total = total;
},
},
});
export const { clearCart, removeItem, increase, decrease, calculate } =
cartSlice.actions;
export default cartSlice.reducer;
How do I implement something like this?
checkoutSlice.js
const initialState = {
purchasedItems: [],
checkoutIsOpen: false,
};
const { cartItems, amount } = useSelector((store) => store.cart); <--HYPOTHETICALLY
const checkoutSlice = createSlice({
name: "checkout",
initialState,
reducers: {
addToCheckout: (state) => {
if (amount >= 1) { <------HERE
state.purchasedItems.push(cartItems); <---HERE
}
},
openCheckout: (state) => {
state.checkoutIsOpen = true;
},
},
});
export const { addToCheckout, openCheckout } = checkoutSlice.actions;
export default checkoutSlice.reducer;
You can't use selectors, what else is left? I've read a ton of previous posts that say it's not possible, but then how do you create functional websites with components that interact with each other? Like in this case with basic shopping app with a checkout cart, how do you get the selected items into the checkout cart? It's just not possible? That doesn't make sense because isn't that basic core functionality of a website? Why wouldn't redux allow this? I feel like there has to be a way.
I think I'm fundamentally misunderstanding here.
Any help? How do I accomplish this?

Related

How to prevent my items from duplicating using Redux Toolkit + Strapi + React?

My Items keep on duplicating on shopping cart and I cant seems to figure out. Hope to get some guidance here and would be a great help for my project!
Below are my code :
CartReducer.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
isCartOpen: false,
cart: [],
count: [],
products: [],
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
setProducts: (state, action) => {
state.products = action.payload;
},
addToCart: (state, action) => {
const item = state.cart.find(
(product) => product.id === action.payload.id
);
console.log(item);
if (item) {
item.count += action.payload.count;
} else {
return {
...state,
cart: [...state.cart, action.payload.product],
};
}
},
Full Github repo : https://github.com/TheKelvinT/Teck-Hong-CS.git
I've tried looking up several sample projects and changing my code based on those but it still does not work.
the problem you have there, that:
item.count += action.payload.count;
this line actually does nothing, because you don't use this down stream, to make it work that way you would need something like that:
const itemIndex = state.cart.findIndex((product) => product.id === action.payload.id);
if (itemIndex !== -1) {
state.cart[itemIndex] = {...state.cart[itemIndex], count: action.payload.count }
} else ...
Just a note that it is not obvious where you keep your count, the line above adds count to item in the cart.
There might be more issues with this code, hover i can recommend in terms of cart use state normalization technique reference. Although that it may look a bit strange, it is quite easy and powerful:
The first thing is to remove products from cart slice, with slices, it's really better to have them as flat as possible, it's much easier to extend after:
/slices/products.js
import { createSlice } from "#reduxjs/toolkit"
const { actions, reducer } = createSlice({
name: 'products',
initialState: [],
reducers: {
setProducts: (state, {payload}) => payload,
}
})
export const productsReducer = reducer;
export const { setProducts } = actions;
Cart slice, is going to have to main properties, the first one is ids an array of id's of products added to cart, the second one would be products it's so called key value pair with key is product id, value is an object with product property and count an integer for count:
/slices/cart.js
import { createSelector, createSlice } from '#reduxjs/toolkit';
const { actions, reducer } = createSlice({
name: 'cart',
initialState: {
ids: [],
products: {},
},
reducers: {
// the payload is { product, count }
addCart: (state, { payload }) => {
if (!state.ids.includes(payload.product.id)) {
state.ids.push(payload.product.id);
state.products[payload.product.id] = { product: payload.product, count: payload.count };
} else state.products[payload.product.id].count += payload.count;
// you should always validate the count e.g. Math.max(0, payload.count)
},
// this would just remove from cart, you can do yourself count change
removeCart: (state, { payload }) => {
if (state.ids.includes(payload.product.id)) {
state.ids = state.ids.filter((id) => id !== payload.product.id);
delete state.products[payload.product.id];
}
},
},
});
export const cartReducer = reducer;
export const { addCart, removeCart } = actions;
// BONUS, is selector that checks if object is in the cart
// const isInCart = useSelector((state) => isInCartSelector(state, id))
export const isInCartSelector = createSelector(
(state, id) => state.cart,
(state, id) => id,
(cart, id) => cart.ids.includes(id)
);

React - Redux-Toolkit items in cart

I am learning react with redux-toolkit. I am stuck with some actions there.
I want to add quantity in Cart, so if I add same item more than once it should be like X1/x2/x3...etc.
And I want to delete items/item but only with the same ID ( when I click delete only delete that one ex. APPLE)
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
hidden: true,
cartItems: 0,
itemsInCart: [],
quantity: 0,
totalCount: 0,
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
removeItem: (state, action) => {},
removeAll: (state) => {
state.cartItems = 0;
state.itemsInCart = [];
state.totalCount = 0;
},
addToCart(state, action) {
state.itemsInCar = state.itemsInCart.push(action.payload);
state.cartItems += 1;
state.totalCount += action.payload.price;
},
showCart: (state) => {
state.hidden = !state.hidden;
},
},
});
export const { showCart, addToCart, removeAll, removeItem } = cartSlice.actions;
export default cartSlice.reducer;
addToCart: (state, action) => {
const itemInCart = state.cart.find((item) => item.id === action.payload.id);
if (itemInCart) {
itemInCart.quantity++;
} else {
state.cart.push({ ...action.payload, quantity: 1 });
}
},

Setting state in zustand persist middleware causing infinite loop

import create from 'zustand';
import createContext from 'zustand/context';
import { persist } from 'zustand/middleware';
let store;
const initialState = {
loading: false,
cart: {
cartItems: {},
invoiceData: {},
count: 0,
},
};
const zustandContext = createContext();
export const Provider = zustandContext.Provider;
// An example of how to get types
/** #type {import('zustand/index').UseStore<typeof initialState>} */
export const useStore = zustandContext.useStore;
export const initializeStore = (preloadedState = {}) => {
return create(
persist(
(set, get) => ({
...initialState,
...preloadedState,
updateCart: (cartData) => {
set({
cart: cartData,
});
},
setLoading: (val) => {
set({
loading: val,
});
},
modifyCart: (product, qty, type) => {
const cartData = get().cart;
// cart operations
set({
cart: tmpCartData,
});
},
}),
{
name: 'cartData',
}
)
);
};
export function useCreateStore(initialState) {
const [cartData, setCartData] = useState(null);
const [userCart, setCart] = useLocalStorage('cartData', {});
const { state: { cart = {} } = {} } = userCart;
if (typeof window === 'undefined') {
return () => initializeStore(initialState);
}
store = store ?? initializeStore(initialState);
useLayoutEffect(() => {
if (initialState && store) {
store.setState({
...store.getState(),
...initialState,
});
}
}, [initialState]);
useLayoutEffect(() => {
(async () => {
store.setState({
...store.getState(),
cart: { ...cart },
loading: true,
});
})();
}, []);
return () => store;
}
This code is inspired by Zustand documentation and by the NextJS and Zustand boilerplate. I need to sync this data with the browser's localstorage. However, calling the 'set' method inside modifyCart causes an infinite render. I have not found enough documentations regarding this.
How should I go about debugging such an issue?

cannot update initalState in redux-toolkit with createAsyncThunk

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"
},

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