I'm attempting to use the rest operator and restructuring to omit an entry in the object. Based on the documentation, rest should no longer include the key entry 575. After the operation, rest still has the same keys as state. I'm not sure what I'm doing wrong. Thanks in advance.
book = {
id: 575,
title: "Vector Calc"
};
state = {
removedBooks: {
46: {
id: 46,
title: "economics"
},
575: {
id: 575,
title: "Vector Calc"
}
}
};
const {
[book.id]: data, ...rest
} = state;
console.log(rest);
EDIT: I am using React and it is not recommended to mutate the state object directly. Why can't I directly modify a component's state, really? among others
The books are part of the removedBooks property, and are not direct children of the state. You need to destructure the removedBooks property as well.
const book = {"id":575,"title":"Vector Calc"};
const state = {"removedBooks":{"46":{"id":46,"title":"economics"},"575":{"id":575,"title":"Vector Calc"}}};
const { removedBooks: { [book.id]: data, ...removedBooks } } = state;
const newState = { ...state, removedBooks };
console.log(newState);
Your destructuring assignment expects a pattern of { 575: data, ...other... } but state actually has { removedBooks: { 575: data, ...other... } }. Add the removedBooks into your destructuring assignment and it works fine.
book = {
id: 575,
title: "Vector Calc"
};
state = {
removedBooks: {
46: {
id: 46,
title: "economics"
},
575: {
id: 575,
title: "Vector Calc"
}
}
};
const { removedBooks: {
[book.id]: data, ...rest
} } = state;
console.log(rest);
Related
I've got an array of objects and when a certain functions called I want to update a specific array within that array of objects with new data.
Heres the call:
const DataReducer = (state, action) => {
switch (action.type) {
case 'ADD_DATA':
return [...state, state[0].data:[...state, {id: Math.floor(Math.random() * 999),
name: action.payload.name,
}]];
}
}
The problem is I keep getting an error "',' Expected", this appears on the : after data
I'm pretty sure this is correct though, I'm using the context api to update some existing state with new names when the addName function is called.
This should spread the existing state and take the specific state from item[0].data, adding the new name to it but I cant seem to get it working.
Any help would be appreciated.
Here's the original data object: [{title: 'Names', data: []}, {title: 'Meal', data: []}]
It might be easier to break up that return. Make a copy of the state, create an object, and add that to the data array, preserving any objects that are already in there. Then return the copy to update the state.
const DataReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case 'ADD_DATA': {
const copy = [...state];
copy[0] = {
...copy[0],
data: [
...copy[0].data, {
id: Math.floor(Math.random() * 999),
name: 'Bob'
}
]
};
return copy;
}
}
}
const state = [{title: 'Names', data: []}, {title: 'Meal', data: []}];
const newState = DataReducer(state, { type: 'ADD_DATA', payload: { name: 'Bob' } });
console.log(newState);
return [
{
...(state[0] || {}),
data: {
id: Math.floor(Math.random() * 999),
name: payload.name
}
},
...state?.slice?.(1)
]
I have a state with the following structure. It contains a list of Workouts and each workout has a list of exercises related to this workout.
I want to be able to do 2 things:
add new exercises to the specific workout from the list of workouts
delete a specific exercise from the specific workout
E.g. In my UI I can add new exercises to Workout with the name Day 2.
So my action payload gets 2 params: workout index (so I can find it later in the state) and exercise that should be added to/deleted from the list of exercises of the specific workout.
State
state = {
workouts: [
{
name: "Day 1",
completed: false,
exercises: [{
name: "push-up",
completed: false
},
{
name: "running",
completed: false
}]
},
{
name: "Day 2",
completed: false,
exercises: [{
name: "push-up",
completed: false
}]
},
{
name: "Day 3",
completed: false,
exercises: [{
name: "running",
completed: false
}]
}]
}
Actions
export class AddExercise implements Action {
readonly type = ADD_EXERCISE
constructor(public payload: {index: number, exercise: Exercise}) {}
}
export class DeleteExercise implements Action {
readonly type = DELETE_EXERCISE
constructor(public payload: {index: number, exercise: Exercise}) {}
}
And I am stuck on the reducer. Can you advise how it should be done properly?
This is how it looks right now (not finalized yet):
Reducer
export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
switch(action.type) {
case WorkoutActions.ADD_EXERCISE:
const workout = state.workouts[action.payload.index];
const updatedExercises = [
...workout.exercises,
action.payload.exercise
]
return {
...state,
workouts: [...state.workouts, ]
}
return {};
default:
return state;
}
}
Thank you!
Please, try something like the following (I included comments within the code, I hope it makes it clear):
export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
switch(action.type) {
case WorkoutActions.ADD_EXERCISE:
// You can take advantage of the fact that array map receives
// the index as the second argument
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const workouts = state.workouts.map((workout, index) => {
if (index != action.payload.index) {
return workout;
}
// If it is the affected workout, add the new exercise
const exercises = [
...workout.exercises,
action.payload.exercise
]
return { ...workout, exercises }
})
// return the updated state
return {
...state,
workouts
}
case WorkoutActions.DELETE_EXERCISE:
// very similar to the previous use case
const workouts = state.workouts.map((workout, index) => {
if (index != action.payload.index) {
return workout;
}
// the new exercises array will be composed by every previous
// exercise except the provided one. I compared by name,
// I don't know if it is accurate. Please, modify it as you need to
const exercises = workout.exercises.filter((exercise) => exercise.name !== action.payload.exercise.name);
return { ...workout, exercises }
})
// return the new state
return {
...state,
workouts
}
default:
return state;
}
}
Redux, "delete Object" is not working properly in nested Object. I am just trying to delete key "b" in key "0". I am able to just delete the key "0", but when I am trying to delete the nested key "b" in "0", is not working. If you have any idea how to solve it, please, let me know!
Code:
export function removeOrder(key1, key2) {
return {
type: 'REMOVE_ORDER',
payload: {key1: key1, key2: key2},
};
}
var initialState = {
0: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
1: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
};
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
const id2 = action.payload.id2;
// Working
//delete state[id1];
// Not working
delete state[id1][id2];
return state;
default:
return state;
}
}
removeOrder(0, 'a');
The expected result:
{
0: {
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
1: {
a: {
title: 'aa',
},
b: {
title: 'bb',
},
c: {
title: 'cc',
},
},
};
You can't delete a field from state like that, as that would mutate the actual state object which is one of the basic no-no's in Redux. From their docs:
https://redux.js.org/usage/troubleshooting#never-mutate-reducer-arguments
It is tempting to modify the state or action passed to you by Redux. Don't do this!
For general patterns see immutable-update-patterns. Specifically for your example you need to create a new state object, spreading the values you do want... An easy way to do this is to use lodash's omitBy to exclude the item with the particular key that you don't want in there anymore.
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
return omitBy(state, (value, key) => key === id1);
default:
return state;
}
}
As others have mentioned in the comments, you can ignore id2 if it's just a sub-key of id1.
If you just want to remove a subkey of id1 then you could use this instead, which is a slight modification on the same approach:
function ordersReducer(state = initialState, action) {
switch (action.type) {
case 'REMOVE_ORDER':
const id1 = action.payload.id1;
const id2 = action.payload.id2;
return {
...state,
[id1]: omitBy(state, (value, key) => key === id2)
};
default:
return state;
}
}
You should not mutate state, you can use spread you don't need to add extra dependencies to your project, omit would be a couple of lines of code:
function omit(state, keys) {
const { [keys[0]]: remove, ...rest } = state;
if (keys.length === 1) {
return rest;
}
return {
...state,
[keys[0]]: omit(remove, keys.slice(1)),
};
}
//test code:
const state = {
0: {
b: {
title: 'bb',
},
c: 'not changed',
},
1: 'not changed',
};
console.log('remove 0', omit(state, ['0']));
console.log('remove 0,b', omit(state, ['0', 'b']));
console.log(
'remove 0,b,title',
omit(state, ['0', 'b', 'title'])
);
As Bravo points out, once you delete the "root" property then all nested properties are also removed.
Other than property deletion you've a state mutation issue since you delete a property and then return the same state object.
You need to return a new copy of state, then you can use a single delete referencing the "full" dynamic path to the second nested key.
case 'REMOVE_ORDER':
const { key1, key2 } = action.payload;
// shallow copy state
const nextState = { ...state };
if (state[key1]) {
// shallow copy the nested state
nextState[key1] = { ...state[key1] }
}
// delete the nested property if it exists
delete nextState[key1]?.[key2];
return nextState;
I am trying to move an object from one array to another. Think of it like adding / moving a friend from non-friend to friend. I have two arrays, which can be seen below, and I am trying to move an object (i.e. a friend) from possible to current via it's 'id'. In the below example, I am trying to move Parker from possible to current with id = 2.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = Object.assign(
{},
state.possible.splice(action.payload.index, 1)
);
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
Since you can remove multiple elements with splice(), it returns an array. Index the result to get the specific object. You don't need to use Object.assign(), that just copies the value (which just converts the array into an object whose properties are the array indexes).
var state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
};
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = state.possible.splice(action.payload.index, 1)[0];
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
state = addFriend(state, {payload: { index: 0 }});
console.log(state);
I'm not sure why you're returning a new state object, since you're modifying the old state in place.
It is not that time-efficient if you want a fast running code. But it follows immutability.
We just ignore the item from possible, and add it to current.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
return {
...state,
current: current.concat(possible[action.payload.index]),
possible: possible.filter((_, index) => index !== action.payload.index)
}
}
state = addFriend(state, {payload: {index: 0}})
console.log(state)
const state = [
{
list: []
}
];
The list is a list of student objects, for example:
list: [
{ id: 1, name: "Mark", attendance: true },
{ id: 2, name: "John", attendance: false }
]
I have a button that triggers a post request to an API to change attendance to true. Post request returns the student object that was changed as in e.g.:
{ id: 2, name: "John", attendance: true }
This works fine and if no errors, will dispatch ATTENDANCE_SUCCESS.
Now, with this kind of set-up:
export function students(state, action) {
let latestState = state[state.length - 1],
newState = Object.assign({}, latestState);
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
// Need to change redux state 'attendance' value to true for a student with ID returned from the POST request
}
}
Initially, I did:
const studentChanged = newState.list.find(function(student) {
return (
student.id ===
action.res.data.id
);
});
studentChanged.attendance = true;
But it mutates the state in the redux store (although I am not sure how it's exactly happening since I assumed newState is already a copy).
What's the proper way?
The following would update a single item in the array. The critical aspect here is that if the id of the item does not match the id from the action payload, it returns the item unaltered, otherwise it updates the attendance property. Array.prototype.map returns a new array so it would be immutable.
export function students(state, action) {
switch (action.type) {
case "ATTENDANCE_SUCCESS":
if (action.res.errorCode == 0) {
return state.map(student => {
// we want to leave non matching items unaltered
if (student.id !== action.res.data.id) {
return student;
}
return { ...student, attendance: true };
});
}
return state;
default:
return state;
}
}
Here is a StackBlitz to demonstrate the functionality.
Hopefully that helps!