Hello created reducer and wondering is this right pattern.
I am trying to:
Add new item into array of objects ACTION_TYPE
Update item in array of objects ACTION_TYPE_EDIT, this one actually should set charity prop and update item in array charities
Wondering if i am on right path, i don't want to mutate, so here is my code.
export const reducerCharities = (
state: CharityReducerState,
action: CharitiesAction
| CharityEditAction
) => {
switch (action.type) {
case ACTION_TYPE_EDIT:
return {
...state,
charity: {
...state.charity,
...(action.charity || {}),
},
charities: [
...state.charities.map(item => item.id === action.charity?.id ? { ...item, ...action.charity } : item)
]
}
case ACTION_TYPE:
return {
...state,
charities: [
...state.charities || [],
...action.charities
]
}
default:
return state
}
}
So my question is will this part mutate state or is there better way to write this?
charities: [
...state.charities.map(item => item.id === action.charity?.id ? { ...item, ...action.charity } : item)
]
Is this right approach how to add new item into array
{
...state,
charities: [
...state.charities || [],
...action.charities
]
}
The easiest (and since 2019 officially recommended) way of doing this would be using the official Redux Toolkit package and the createSlice method.
Within that, you can just mutate and don't have to care about manual immutable updates - the package takes over for you.
The code could look like
const charitiesSlice = ({
name: 'charities',
initialStae: myInitialState,
reducers: {
edit(state, action: PayloadAction<Charity>){
const charity = state.charities.find(item => item.id === action.payload.id)
if (charity) {
Object.assign(charity, action.payload)
}
state.charity = action.payload
}
}
}
export const { edit } = charitiesSlice.actions
export const reducerCharities = charitiesSlice.reducer
Related
I've set up a Store.js with a players array. I want to add players to the array as I select them but still be able to set the array to empty if I clear the array.
Here is some code from my Store.js
const initialState = {
playerCollection: [],
}
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_PLAYER_COLLECTION':
return { ...state, playerCollection: action.value };
default:
return state;
}
}
Here is some code from my Players.js
for(let i=0; i<players.length; i++){
let player = players[i];
if(player.position === state.posAbbr && player.status === 'ACT'){
let newPlayer = createNewPlayer(roster, player);
dispatch({ type: 'UPDATE_PLAYER_COLLECTION', value: [...state.playerCollection, newPlayer] });
return;
}
}
My dispatch line is only adding a single player to the playerCollection array.
I also want to, like I said above, be able to set the array to [] if I clear it.
Define a clear playerCollection action, and set array to empty in reducer for that action:
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_PLAYER_COLLECTION':
return { ...state, playerCollection: action.value };
case 'CLEAR_PLAYERS':
return {...state,playerCollection: []};
case "ADD_PLAYER" :
return {...state,playerCollection : [...state.playerCollection,action.value]}
default:
return state;
}
}
I know that redux trigger react component to re-render once state of that component get changed but this doesn't happen in my situation.
Action
const addToCart = (product, cartProducts) => {
let newCartProducts = [...cartProducts, product];
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeated = acc.find(p => p.id === cur.id);
if(repeated) repeated.quantity++;
else acc.push(cur);
return acc;
}, []);
return {
type: ADD_TO_CART,
payload: newCartProducts
}
}
Reducer:
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return action.payload;
default:
return state;
}
}
The reducer returns a new state every time the action dispatched from the component but i need to close the cart and open it again to get the effect, redux does not update the product quantity simultaneously??
You are modifying the existing elements in the state.
Use
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeatedIndex = acc.findIndex(p => p.id === cur.id);
const repeated = acc[repeatedIndex];
if (repeated) {
acc[repeatedIndex] = {
...repeated,
quantity: repeated.quantity + 1
};
} else acc.push(cur);
return acc;
}, []);
You array is recreated each time, but the objects inside it are not. So when you modify their internals you need to notify that the specific object has changed.
Refactor logic to the reducer and set the quantity here:
const addToCart = product => {
return {
type: ADD_TO_CART,
payload: product,
};
};
//I assume state is an array of products on your cart
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
const { id } = action.payload;
return state.map(p => p.id).includes(id)
? //product is already on card add quanity
state.map(p =>
p.id === id
? { ...p, quantity: p.quantity + 1 }
: p
)
: state.concat({ ...action.payload, quantity: 1 }); // add product
default:
return state;
}
};
so I have a reducer that is adding to array
create reducer :
export default (itemsList = [], action) => {
if (action.type === 'ADD_ITEM') {
return [...itemsList, action.payload]
}
return itemList
}
deleting reducer (99% that something is wrong here, but I have no idea what ):
export default (itemList = [], action) => {
if (action.type === 'DELETE_ITEM') {
return [...itemList, itemList.filter(item => item !== action.payload)]
}
return itemList
};
action/index.js:
export const addItemToList = item => {
return {
type: 'ADD_ITEM',
payload: selectedItem
}
};
export const deleteItemFromList = item => {
return{
type: 'DELETE_ITEM',
payload: selectedItem
}
};
let say I have
itemList = [ 'abc', 'xyz', 'qwe' ]
and I want to use deleteItem('xyz') to delete 'xyz' from itemList
While deleting you just need to return the filtered list and not use spread operator too.
export default (itemList = [], action) => {
if (action.type === 'DELETE_AUTHOR') {
return itemList.filter(item => item !== action.payload)
}
return listOfAuthorsSelected
};
Array.filter() returns a new array with given filter condition and not mutate the existing array. You don't have need to use ...itemList(spread operator). Here you are actually adding a sub array each time.
Here is a simple running example
var array1 = ["abc", "def" , "ghi"];
var array2 = [...array1, array1.filter((item) => {
return item !== "def"
})];
document.write(array2);
// correct way to filter
var array3 = ["abc", "def" , "ghi"];
var array4 = array3.filter((item) => {
return item !== "def"
});
document.write("<hr/>"+array4);
I'm trying to implement a simple shopping cart functionality using Redux and pure Javascript. I've split my reducers in two, one for UI and one for the cart-functionality.
My problem is, when adding an item to the cart, it shows up fine in state, but when I add another item, it overwrites the previous item in state. I've struggled with this for a while, and tried multiple solutions.
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
shop: {
purchasedItems: [],
shopItems: [...shop.items]
}
}
function shopReducer(state = initialState, action) {
switch (action.type) {
case BUY_ITEM:
const clickedItem = state.shopItems.filter( item => item.id == action.id);
return {...state, purchasedItems: [...state.purchasedItems, clickedItem ] }
function buyItemShop(id) {
return {
type: BUY_ITEM,
id
}
}
Object destructuring does not perform a deep merge.
Anyways, you have some other issues because you are not using the proper keys from the state.
Should be instead:
return {
...state,
shop: {
purchasedItems: [...state.shop.purchasedItems, clickedItem],
shopItems: state.shop.shopItems,
},
}
This works - simply needed to spread the clickedItem object in the return statement.
function shopReducer(state = initialState.shop, action) {
switch (action.type) {
case BUY_ITEM:
let clickedItem = state.shopItems.filter( item => item.id == action.id);
return {...state, cartItems: [...state.cartItems, ...clickedItem ] }
I'm wondering if you could help me with this problem if possible. I am trying to delete an item from the Redux state. I have passed in the ID of the item that the user clicks via action.data into the reducer.
I'm wondering how I can match the action.data with one of the ID's within the Redux state and then remove that object from the array? I am also wondering the best way to then set the new state after the individual object has been removed?
Please see the code below:
export const commentList = (state, action) => {
switch (action.type) {
case 'ADD_COMMENT':
let newComment = { comment: action.data, id: +new Date };
return state.concat([newComment]);
case 'DELETE_COMMENT':
let commentId = action.data;
default:
return state || [];
}
}
Just filter the comments:
case 'DELETE_COMMENT':
const commentId = action.data;
return state.filter(comment => comment.id !== commentId);
This way you won't mutate the original state array, but return a new array without the element, which had the id commentId.
To be more concise:
case 'DELETE_COMMENT':
return state.filter(({ id }) => id !== action.data);
You can use Object.assign(target, ...sources) and spread all the items that don't match the action id
case "REMOVE_ITEM": {
return Object.assign({}, state, {
items: [...state.items.filter(item => item.id !== action.id)],
});
}
You can use Try this approach.
case "REMOVE_ITEM":
return {
...state,
comment: [state.comments.filter(comment => comment.id !== action.id)]
}
For anyone with a state set as an Object instead of an Array:
I used reduce() instead of filter() to show another implementation. But ofc, it's up to you how you choose to implement it.
/*
//Implementation of the actions used:
export const addArticle = payload => {
return { type: ADD_ARTICLE, payload };
};
export const deleteArticle = id => {
return { type: DELETE_ARTICLE, id}
*/
export const commentList = (state, action) => {
switch (action.type) {
case ADD_ARTICLE:
return {
...state,
articles: [...state.articles, action.payload]
};
case DELETE_ARTICLE:
return {
...state,
articles: state.articles.reduce((accum, curr) => {
if (curr.id !== action.id) {
return {...accum, curr};
}
return accum;
}, {}),
}
In my case filter worked without () and {} and the state was updated
case 'DELETE_COMMENT':
return state.filter( id => id !== action.data);