I am trying to make a system like shopping cart with redux and react. The products are stored in a redux slice array as a whole object. The product object goes like this:
This is my code for my checkbox input
const products = useSelector((state) => state.prodSlice.value)
const handleChange = (event) => {
const button = event.target
const isActive = button.checked
const itemName = event.currentTarget.id
const items = products.items
const itemsArr = {}
items.forEach((items) => {
if (items.productName === itemName) {
itemsArr['productName'] = items.productName
itemsArr['total'] = items.total
itemsArr['quantity'] = items.quantity
if (isActive) {
dispatch(checkout({products: itemsArr}))
} else {
dispatch(removeItem({products: itemsArr}))
}
}
})
}
When adding products to the array, there is no problem,
However, when I uncheck an item, and get the value of array it returns just an empty array instead of removing just 1 item.
I just want to delete that one item from the array, here is my redux slice code
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
value: {
products: [],
}
}
export const checkOut = createSlice({
name: "checkout",
initialState,
reducers: {
checkout: (state, action) => {
state.value.products.push(action.payload)
},
removeItem: (state, action) => {
state.value.products = state.value.products.filter((products) => products.produdctName !== action.payload.productName)
}
}
})
export const { checkout, removeItem } = checkOut.actions
export default checkOut.reducer
I hope someone can help me pls
your removeItem reducer should simply return the filtered array
removeItem: (state, action) => {
return state.value.products.filter((products) => products.productName !== action.payload.productName)
}
Related
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)
);
I do not understand, the value is dispatch but useSelector does not update, it's only showing an empty value.
In the above image, the cart contains the value.
see this above image, console Array does not contain any values.
Code:
const dispatch = useDispatch();
const selectorCart = useSelector((state) => state.cart);
const selectorLogin = useSelector((state) => state.login);
function handleAddItemInCart(product) {
let isProductAllReadyExit = true;
for(let item of selectorCart) {
if (product.id === item.id && product.title === item.title) {
isProductAllReadyExit = false;
break;
}
}
if (isProductAllReadyExit) {
dispatch(addItemInCart(product));
console.log("2. Selector Cart Value : ", selectorCart);
handleAddCartItemSave();
}
cartslice
import { createSlice } from "#reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: [],
reducers : {
addItemInCart : (state, action) => {
state.push(action.payload);
},
removeItemInCart : (state, action) => {
return state.product.filter(product => product.id !== action.payload && product.title !== action.payload.title);
},
},
});
export const {addItemInCart, removeItemInCart} = cartSlice.actions;
export default cartSlice.reducer;
What is this mistake in the above code?
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
items: [],
};
export const basketSlice = createSlice({
name: "basket",
initialState,
reducers: {
// actions & reducers; actions named same as reducers in redux toolkit
addToBasket: (state, action) => {
state.items = [...state.items, action.payload];
},
removeFromBasket: (state, action) => {
const index = state.items.findIndex(
(basketItem) => action.payload.id === basketItem.id
);
let newBasket = [...state.items];
if (index >= 0) {
// item exists in the basket; remove it
newBasket.splice(index, 1);
} else {
// else the item doesn't exist
console.warn(
`Can't remove product (id: ${action.payload.id}) as it's not in the basket.`
);
}
state.items = newBasket;
},
},
});
export const { addToBasket, removeFromBasket } = basketSlice.actions;
export const basketActions = basketSlice.actions;
export const selectItems = (state) => state.basket.items;
export const selectTotal = (state) =>
state.basket.items.reduce((total, item) => total + item.price, 0);
export default basketSlice.reducer;
Where is the passed in "state" variable coming from? I don't understand how this works. A function is being returned here and React is injecting the state as the first argument? But since I'm defining the function, how does React know to do this?
Does every returned function have state as the first property? But even then, I don't understand how this will work as state is coming from the above basketSlice.
I see, the function declaration has access to the global state, meaning all slices.
In this example we're getting the basket state via the state.basket reference and the function declaration is just a helper/wrapper function that doesn't need to even be in the basketSlice module.
I tried the following in another module and it works well.
const items = useSelector((state) => state.basket.items);
Now I understand.
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.
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"
},