I'm working on react app with redux. I want to delete multiple item from array. I write below code in my reducer which delete single item from array but i want to delete multiple item.
case DELETE_LINK:
let dltLink = state.filter(item => {
return item._id !== action.data._id
})
return {
...state,
parentFolderlinks: dltLink
};
It seems you want to filter links from state.parentFolderlinks, say you have the ids in action.data.ids, you could
case DELETE_LINK:
const parentFolderlinks = state.parentFolderlinks.filter(item => {
return !action.data.ids.includes(item._id);
});
return {
...state,
parentFolderlinks
};
On what basis would you like to filter items? I assume that multiple items will not have the same id.
Below example shows how we can filter multiple items in redux. In this case, foods state with items that has type as fruit and removes everything else.
// initial state with all types of foods
const initialState = {
"foods": [
{
name: "apple",
type: "fruit"
},
{
name: "orange",
type: "fruit"
},
{
name: "broccoli",
type: "vegetable"
},
{
name: "spinach",
type: "vegetable"
},
]
}
// sample reducer that shows how to delete multiple items
export default (state = initialState, { type, payload }) => {
switch (type) {
// delete multiple items that does not have type fruit
// i.e both brocolli and spinach are removed because they have type vegetable
case DELETE_ITEMS_WITHOUT_TYPE_FRUIT:
const onlyFruits = state.foods.filter(food => food.type === "fruit");
return {
...state,
foods: onlyFruits
}
}
}
you could map over the state and run it through a function that works out if you want to keep it or not (I don't know what your logic is for that) then return the array at the end
const keepThisItem =(item) => {
return item.keep
}
case DELETE_LINK:
let itemsToKeep = []
let dltLink = state.map(item => {
if(keepThisItem(item){
itemsToKeep.push(item)
}
return itemsToKeep
})
Related
I am trying to create shopping basket through Redux Toolkit. I am finding it hard to understand this piece of code that what is purpose of all this code. Specifically those if conditions. Cant understand how add and remove reducer is working
const basketSlice = createSlice({
name: "basket",
initialState: INITIAL_STATE,
reducers: {
add: (state, action) => {
return state.map(item => {
if (item.id !== action.payload.id) {
return item
}
return {
...item,
added: true
}
})
},
remove: (state, action) => {
return state.map(item => {
if (item.id !== action.payload.id) {
return item
}
return {
...item,
added: false
}
})
}
}
})
You should check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Basically it loops over the state items, creating a new array from what is returned in each iteration of the loop.
So, what it does for, say, the remove reducer:
Loop over each item in state, each time returning something that will be an entry in the new array
The if section checks if the id of the current loop element is the same than the one we want to remove: if it's not the same ID, we return the item "as is", if it's the same ID, we return added: false so we know it was removed.
In the end, you get a new array that was processed through this map function, allowing to do whatever check you need to.
Say I have an array with 3 items:
const state = [
{ id: 12, name: "Fancy Phone", added: true, },
{ id: 54, name: "Leather Jacket", added: true, },
{ id: 564, name: "AI World-Dominating Robot", added: true, },
]
And I want to remove the "AI World-Dominating Robot" because I don't want anymore trouble:
// Create a new array from the .map
return state.map(item => {
// here we loop over each item one by one
// IF the ID in the action payload (thus the ID you want to remove) is not the same as the current item ID, we don't want to remove it
if (action.payload.id !== item.id) {
return item // so we return the item "as-is", and as we returned something, the .map loop moves to the next item
}
return { ...item, added: false } // otherwise, we set "added: false" to flag the fact it's removed
I am working on a simple cart function with react-redux, and I have an object that is structured as below:
{
0: { // product ID
"S" : 1, //product variant and item count
"M" : 1
},
1: {
"L":1
"XL": 5
},
}
I wanted to remove the property based on user action but I was not able to achieve that so far.
Attempt 1: delete function will remove everything within the state instead of removing the selected property.
case REMOVE_PRODUCT_FROM_CART:
let newObject = Object.assign({}, state)
return delete newObject[productId][varient];
Attempt 2: only managed to set the property to null but still not able to remove the item.
case REMOVE_PRODUCT_FROM_CART:
return {...state,
[productId]: {
// check if property exists
...(state?.[productId] ?? {}),
[varient]: null
}
Is there any way to remove the desired property with the spread operator?
Here is one way to do it:
case REMOVE_PRODUCT_FROM_CART:
const newObject = {...state[productId]}
delete newObject[varient]
return {...state, [productId]:newObject}
Here is another way to do it:
const state = {
22: {
ok: 88,
remove: 22,
},
};
const productId = 22;
const varient = 'remove';
const { [varient]: ignore, ...product } = state[productId];
console.log('new state', {
...state,
[productId]: product,
});
Here is another option using Object.entries and Object.fromEntries
const state = {
pId: {
k1: 'v1',
k2: 'v2',
k3: 'v3',
},
};
const reducer = (productId, variant) => {
return {
...state,
[productId]: Object.fromEntries(
Object
.entries(state[productId])
.filter(([key, val]) => key !== variant)
),
}
}
console.log(reducer('pId', 'k2'))
I've got an initial array, which can be added to and deleted from, no problems there..
const initialItems = [
{
id: Date.now(),
text: 'Get milk',
},
{
id: Date.now(),
text: 'Get eggs',
},
]
..but I'm trying to figure out how to edit the text effectively of one of the items using a dispatch function.
My dispatch looks like this:
const editItemHandler = () => {
dispatch({
type: 'EDIT_ITEM',
id: Date.now(),
text: itemInputRef.current.value,
index,
})
}
Which is just passing the value of an input
<input
autoFocus
type='text'
ref={itemInputRef}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setToggle(!toggle)
}
if (e.key === 'Enter') {
// Dispatch
editItemHandler()
setToggle(!toggle)
}
}}
/>
My reducer file looks like this:
const itemReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM': {
return [
...state,
{
id: action.id,
text: action.text,
},
]
}
case 'EDIT_ITEM': {
// Attempt 1
return [...state.splice((item, index) => index, 1, action.text)]
// Attempt 2
return [
...state.filter((item, index) => index !== action.index),
{
id: action.id,
text: action.text,
},
]
}
case 'DELETE_ITEM': {
return [...state.filter((item, index) => index !== action.index)]
}
default: {
return state
}
}
}
export default itemReducer
I've commented in 2 approaches I've already tried in the 'EDIT_ITEM' type.
Approach 1 just deletes the item and adds a new valued one albeit at the bottom of the array, which isn't what I want so I'd have to try and reorder after.
Approach 2 is using splice, which I thought was what would work for replacing an item with the specified value. However all it returns is ONLY the 'edited' with the original text (so not even edited), and deletes everything else.
How am I using this function incorrectly, or is there a better approach to editing an item in place? I'm obviously doing something wrong but can't figure out what. I've searched about and tried various approach to no avail.
Ideally I'd want the item to also keep the same id as before as well, so how to keep that would be a plus.
To update an item in an array you have several choices :
case 'EDIT_ITEM': {
// using map
return state.map((item, i) =>
i === action.index ? { id: action.id, text: action.text } : item
// using slice
return [
...state.slice(0, action.index),
{ id: action.id, text: action.text },
...state.slice(action.index+1)
]
This is an incorrect use of splice
return [...state.splice((item, index) => index, 1, action.text)]
because splice return an array containing the deleted elements, and it doesn't accept an function as first argument but the index at which to start changing the array.
the correct way with splice :
case 'EDIT_ITEM': {
// using splice
let newState = [ ...state ]
newState.splice(action.index, 1, { id: action.id, text: action.text })
// or you can directly do
newState[action.index] = { id: action.id, text: action.text }
// and return the new state
return newState;
I'm new to Redux and am having some difficulty composing a working reducer for my situation.
My current state looks like this
export const userData = {
userID: '12345678',
userDetails: {
firstName: 'Joe',
surname: 'Bloggs'
},
currentGames: [
{
gameId: 'G-00000001',
gameSelections: [
{
subgameId: '',
selection: ''
}
]
}
]
};
My action looks like this
function selectWinner (gameId, subgameId, selection) {
return {
type: SELECT_WINNER,
gameId,
subgameId,
selection
}
}
The aim is to be able to add/update the objects in the gameSelections array.
There may be more than one Object in the currentGames array also.
I've heard I should use .map but I'm not really sure how.
You're on the right track for using .map() to iterate over the array of objects. It also looks like your action-creator has all the necessary parameters to update your reducer state.
Your reducer can look something like this:
const userReducer = (state=userData, action) => {
switch(action.type){
case SELECT_WINNER:
return {
...state,
currentGames: [...state.currentGames].map((game) => {
if(game.gameId == action.gameId){
return {
...game,
gameSelections: [...game.gameSelections].map((gameSelection) => {
if(gameSelection.subgameId == action.subgameId){
return {
...gameSelection,
selection: action.selection
}
} else {
return gameSelection
}
})
}
} else {
return game
}
})
}
default:
return state
}
}
Kind of messy, but would get the job-done with a deeply nested state.
Add item to array:
case'ADD_ITEM':
return {
...state,
some_arr: [...state.some_arr, action.payload]
}
update spicific item in array:
case 'UPDATE_ITEM':
return {
...state,
some_arr: state. some_arr.map(
(item, index) => index === specific_index
? {...item, ...action.payload}
: content
)
}
Deep cloning of the state is required.
useful link-https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
const reducer = (state = userData, action) => {
switch (action.type) {
case CASENAME:
return {
userID: state.userID,
userDetails: {
...state.userdetails
},
currentGames: [
{
gameId: action.gameId,
gameSelections: [
{
subgameId: action.subgameId,
selection: action.selection
}
]
}
]
};
}
}
I need to create a reducer that toggles the state of done using the id or index of the todo
state = {
todos: [
{
title: "eat rice",
done: false,
id: 1
},
{
title: "go fishing",
done: true,
id: 2
},
{
title: "drink coffee",
done: false,
id: 3
}
]
}
I tried this but it mutates the state, the payload being the index of the object in the array.
case "DONE":
const todos = [...state.todos];
todos[action.payload].done = !todos[action.payload].done;
return {
...state,
todos: todos
};
You could use a map function instead. The function will generate a new array which you can use to replaces todos with.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
// Based on your code, I assume action.payload is the index of the todo in the array of todos
if (index === action.payload) {
const newTodo = {...todo};
todo.done = !todo.done;
return todo;
}
return todo;
});
return {
...state,
todos: newTodos,
};
If you don't want to iterate over every todo, you could do something else such as using slice to create a copy of the array and then change the one value:
case "DONE":
const newTodos = todos.slice();
const updatedTodo = {...newTodos[action.payload]};
updatedTodo.done = !updatedTodo.done;
newTodos[action.payload] = updatedTodo;
return {
...state,
todos: newTodos,
};
Found the answer. Thanks for the contributions.
case "DONE":
const newTodos = state.todos.map((todo, index) => {
if (index === action.payload) {
const newTodo = { ...todo };
newTodo.done = !newTodo.done;
return newTodo;
}
return todo;
});
return {
...state,
todos: newTodos
};
Using the spread operator or map will create a new array but will not automatically clone the contained objects, as JavaScript follows "pass by reference". You'd have to clone the object as well. So maybe something like
case "DONE":
const todos = state.todos.map((todo, index) => {
const newTodo = {...todo};
if (action.payload === index) {
newTodo.done = !todo.done;
}
return newTodo;
});
return {
...state,
todos,
};
Of course you could also use a clone utility or something like Immutable.js.