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'))
Related
So I have the following reducer
const objectType = (state = {type: 0, image:defaultImage, moreOptions: {tap: 0, griff: 0} },
action) => {....
case 'CHANG_OPTIONS':
return state = {...state, moreOptions: {tap:action.tap, griff: action.griff}}
This is the action, so I get a dynamic category and assign the id of the product.
export const changeOptions = (category, id) => {
return {
type: 'CHANG_OPTIONS',
[category]: id,
}
}
An example of dispatch would be
dispatch(changeOptions('tap', 0))
Now whenever I click on a tap or a griff, my object remove the other category from the list.
Here is a screenshot from the Redux Debugger tool
I'm sure that the problem is in my reducer:
moreOptions: {tap:action.tap, griff: action.griff} Is there a way I can spread the object and update only the one that was changed?
It's because you're overwritting both tap and griff value regardless of their input value. Try below.
const newOptions = {};
if (action.tap) {
newOptions.tap = action.tap;
}
if (action.griff) {
newOptions.griff = action.griff;
}
return (state = {
...state,
moreOptions: {
...state.moreOptions,
...newOptions
}
});
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
})
I have an array of objects. I find the object I need to replace but can't replace it in state. The original object stays untouched and the object I want to replace it with is added to the end of the array. I'm use React hooks. Thanks
case types.ADD_USERNAME:
let task = action.payload
let assignedTask = state.tasks.find(el => el.id === task.id)
let index = state.tasks.indexOf(assignedTask)
return {
...state,
tasks: [
...state.tasks,
assignedTask = {
...assignedTask,
user: task.user.name
}
]
}
You can make use of map instead of going by index and find element to update the state which in my opinion is a cleaner approach
case types.ADD_USERNAME:
let task = action.payload;
return {
...state,
tasks: state.tasks.map(el => {
if(el.id == task.id) {
return { ...el, user: task.user.name}
}
return el;
});
}
However if you wish to go by the index and find element approach, you need to slice the task array and update the specific index element
case types.ADD_USERNAME:
let task = action.payload
let index= state.tasks.findIndex(el => el.id === task.id);
return {
...state,
tasks: [
...state.tasks.slice(0, index - 1),
{
...state.tasks[index],
user: task.user.name
}
...state.tasks.slice(index + 1)
]
}
You could achieve this by using map and matching your object by id or any other unique identifier:
case types.ADD_USERNAME:
let task = action.payload;
return {
...state,
tasks: state.tasks.map(t => {
if (t.id === task.id) {
return {
...t,
user: task.user.name
};
}
return t;
})
};
This is something also called as deep state update. Where I have to update nested state array.
I am implementing a simple redux application. Here I want to update the state which is nested array of object. My reducer function takes state, action. I have to update responses property of state with new value. I tried to map/iterate the state but it isnt working for me. Is there a way to update those specific values and return update state.
const sampleData = [{
"id": 1,
"text": "Hobby",
"answers": [{
"id": 1,
"text": "Chess",
"responses": 5
}]
}];
const action = {
type: "VOTE",
questionId: 1,
answerId: 3
};
This is handleSubmit function I am calling when Submit button is clicked on form.
handleSubmit(e){
const store = createStore(hobbyReducer, hobby); // created store here
var k = (document.querySelector('input[name = "hobby"]:checked').value);
store.dispatch({type:"SUBMIT", text: k, id: 1}); // Dispatching action to reducer
store.subscribe(()=>{
console.log(store.getState());
});
}
Here is reducer part of program:
function hobbyReducer(state, action) {
switch(action.type){
case "SUBMIT":
return{
...state,
answers: state.answers.map(e=> (e.text === action.text && e.answers.id === action.id)?
{ ...answers,
responses: (e.responses+1)} :
hobby )
}
break;
default:
return state;
}
}
initial state = sampleData; // Array object given above
I am unable to update the responses property which is in a nested array
This is the code I wanted to write, after some research I finally did what was required. Although this is not solution in terms of time complexity.
`
case "SUBMIT":
const updatedState = state.map(e =>{
if(e.id === action.id)
return{...e,
answers: e.answers.map(f =>{
if(f.text === action.text){
return{
...f,
...{responses: f.responses + 1},
}
}
return f;
})
}
return e;
})
console.log("updatedstate", updatedState)
return updatedState
Just an error in your map I think:
function hobbyReducer(state, action) {
switch(action.type) {
case "SUBMIT":
return {
...state,
answers: state.answers.map(answer => {
if (answer.text === action.text && answer.id === action.id) {
answer.response = answer.response + 1;
}
return answer;
});
}
default:
return state;
}
}
I am trying to add notes to a task object but what I have so far adds it to all the tasks. When I try different ways, it doesn't compile. The Object.assign doesn't like coming after the .push()
When it adds to all task:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
const { notes } = task;
const { text } = action;
notes.push({
text,
id: notes.length,
})
return task.id === action.id ?
Object.assign({}, { task, notes }) : task
})
When it doesn't compile:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
return task.id === action.id ?
const { notes } = task;
const { text } = action;
notes.push({
text,
id: notes.length,
})
Object.assign({}, { task, notes }) : task
})
You almost never want to use Array.push() in a reducer, because that directly mutates the existing array, and direct mutations generally break UI updates (see the Redux FAQ). You could use push() on a new copy of the old array, but most examples don't use that approach. Most of the time, the suggested approach is to use const newArray = oldArray.concat(newValue), which returns a new array reference containing all the old items plus the new item.
Beyond that, keep in mind that when updating nested data immutably, every level of nesting needs to have a copy made and returned.
Haven't actually tested this, but I think your code needs to look roughly like this example:
let taskReducer = function(tasks = [], action) {
switch (action.type) {
case 'ADD_NOTE':
return tasks.map((task) => {
if(action.id !== task.id) {
return task;
}
const { notes } = task;
const { text } = action;
const newNotes = notes.concat({id : notes.length, text});
const newTask = Object.assign({}, task, {notes : newNotes});
return newTask;
}
default : return tasks;
}
}