Peter Zacharias Peter Zacharias - 1 month ago 6
React JSX Question

How to deleting Entries from Reducer / how to return a new state? (React / Redux)

My reducer by default returns this (it's from a tutorial):

return [
{
id: 1,
first: "Bucky",
last: "Roberts",
age: 71,
description: "Bucky is a React developer and YouTuber",
thumbnail: "http://i.imgur.com/7yUvePI.jpg"
},
{
id: 2,
first: "Joby",
last: "Wasilenko",
age: 27,
description: "Joby loves the Packers, cheese, and turtles.",
thumbnail: "http://i.imgur.com/52xRlm8.png"
},
{
id: 3,
first: "Madison",
last: "Williams",
age: 24,
description: "Madi likes her dog but it is really annoying.",
thumbnail: "http://i.imgur.com/4EMtxHB.png"
}
]
}


I now want to be able to delete user entries from this 'list'. So I went ahead and built a delete user action creator etc., so in addition to the above data, there is now this code preceding it:

switch (action.type) {
case 'USER_DELETED':
console.log("Hello from reducer-users");
let newState = Object.assign({}, state, );
// console.log(newState == state);
// console.log(newState.users == state.users)
// delete newState.users;
return newState;
break;
}


The idea is that if someone clicks 'DELETE USER', this function is called and a new state is returned with the appropriate user deleted. It also works in the sense that the function is being called (the console.log is displayed). However, I am not sure how to go about deleting a user from this list now? I understand that I need to copy the current state and then delete the user from the copy, & then return the copy so it becomes the new state. Is this correct? If so, I do not know how exactly to dothis. If I try to execute what I have written above, which was just something s.b. on stackoverflow wrote, I get an error:


user-list.js?1cc2:11 Uncaught TypeError: this.props.users.map is not a
function(…)renderList @ user-list.js?1cc2:11render @
?d41d:33(anonymous function) @
ReactCompositeComponent.js?cd59:793measureLifeCyclePerf @
ReactCompositeComponent.js?cd59:74_renderValidatedComponentWithoutOwnerOrContext
@ ReactCompositeComponent.js?cd59:792_renderValidatedComponent @
ReactCompositeComponent.js?cd59:819_updateRenderedComponent @
ReactCompositeComponent.js?cd59:743_performComponentUpdate @
ReactCompositeComponent.js?cd59:721updateComponent @
ReactCompositeComponent.js?cd59:642receiveComponent @
ReactCompositeComponent.js?cd59:544receiveComponent @
ReactReconciler.js?6bfa:126_updateRenderedComponent @
ReactCompositeComponent.js?cd59:751_performComponentUpdate @
ReactCompositeComponent.js?cd59:721updateComponent @
ReactCompositeComponent.js?cd59:642performUpdateIfNecessary @
ReactCompositeComponent.js?cd59:558performUpdateIfNecessary @
ReactReconciler.js?6bfa:158runBatchedUpdates @
ReactUpdates.js?ce09:151perform @ Transaction.js?6dff:138perform @
Transaction.js?6dff:138perform @
ReactUpdates.js?ce09:90flushBatchedUpdates @
ReactUpdates.js?ce09:173closeAll @ Transaction.js?6dff:204perform @
Transaction.js?6dff:151batchedUpdates @
ReactDefaultBatchingStrategy.js?ef70:63batchedUpdates @
ReactUpdates.js?ce09:98dispatchEvent @ ReactEventListener.js?2365:150

Answer

You're correct in saying that your reducer should return a new copy of the state, without mutating the current one. This approach have a few very nice benefits (see here: http://redux.js.org/docs/Glossary.html#reducer)

Assuming the state managed by this reducer is an array, to delete a user from your list, you can do the following:

switch (action.type) {
    case 'USER_DELETED':
        return state.filter(user => user.id !== action.userIdToDelete);
}

The key point is that Array.filter returns a new array applying the callback to each element, deciding which one to keep/filter, it doesn't mutate the original array. Check out the docs here https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

You could be tempted to use Array.splice instead (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) but that would be wrong in this case, since splice "changes the content of an array by removing existing elements and/or adding new elements.", so it does mutate the original one.

Note that you need to pass userIdToDelete in your action payload.

Also, you don't need break as you're already using return.

As for the exact reason to the error you posted, the problem is that at the moment your reducer is returning an object instead of an array: let newState = Object.assign({}, state, ); Then, somewhere in your components you're reading from the state and you're trying to call map on your state, which is not an array anymore (hence no map method defined for this.props.users

Another suggestion:

I'd really recommend you to start using plain Js inside your reducers, always being careful about methods that mutate objects in place. After you know what you're doing, sometimes in Js working with immutable data structures can be clumsy, so you can start looking into using these libraries to ease the pain for more complicated scenarios (but only if you really need), ordered by complexity (obviously in my opinion):

Comments