Remove Item Without Mutating State in Redux - javascript

The first thing I tried was this:
const initialState = {
items: {},
showCart: false,
showCheckout: false,
userID: null
};
export default function reducer(state=Immutable.fromJS(initialState), action) {
case 'REMOVE_FROM_CART':
return state.deleteIn(['items', String(action.id)]);
}
When console logging the deleteIn above, it does actually remove the item from the Map correctly. However, the app doesn't re-render again, because I assume I'm mutating the state(?). (mapStateToProps gets called, but no new state).
So next I tried this:
case 'REMOVE_FROM_CART':
const removed = state.deleteIn(['items', String(action.id)]);
const removeItemState = {
...state,
items: { removed }
}
return state.mergeDeep(removeItemState);
But I'm just adding the deleted item to the items again, creating a duplication.
How can I handle this?

Have you tried removing the item after you've deeply cloned the state?
case 'REMOVE_FROM_CART':
const removeItemState = {
...state
items: {
...state.items
}
};
delete removeItemState.items[String(action.id)];
return removeItemState;

How about reduce?
case 'REMOVE_FROM_CART':
return {
...state,
items: Object.keys(state.items).reduce((acc, curr) => {
if (curr !== action.id) acc[curr] = state.items[curr];
return acc;
}, {})
};

Posting more code (such as my reducers setup) may have helped more, but here's what was going on:
First, this code was the right way to remove the item from the state.
return state.deleteIn(['items', String(action.id)]);
However, because I was using the immutable library and not redux-immutable for my combineReducers, my state was not properly being handled. This was allowing me to do things like state.cart.items (in mapStateToProps) where really I should've been using state.getIn(['cart', 'items']).
Changing that magically made the delete work.
Thanks to #jslatts in the Reactiflux Immutable Slack channel for help with figuring this out!

Related

How to stop mutating state?

This is the first time I've encountered mutability. I have state items - an object with keys that go as id, through allIds I find all id items that need to change the date, but they change all items at once, apparently this is due to mutability and I don’t know how to fix it ... I will really appreciate the help!
const allIds = getSubTasksId(Object.values(state.items), payload.id);
allIds.forEach((id) => (state.items[id].date.current = payload.date));
return {
...state,
items: { ...state.items },
};
Your state.items[id].date.current = payload.date statement is responsible for mutation.
One of the possible solution.
const updatedItems = {};
allIds.forEach((id)=> updatedItems[id] = payload.date);
return {
...state,
items: updatedItems
}

Problems achieving required result of using the spread (...) operator with state object

I have a pimRegistration state initialization as shown in the chrome redux-devtools screen capture below. The nesting being referenced is pimRegistration (state.domain.patient):
I updated the patient.name object with the following spread operator statement:
store.update((state) => ({
...state,
...patientPath,
...{ [property]: value },
}));
...where property is the "name" property of the patient object with value. After the update, the following screenshot shows the new state:
Note that the original patient object (purple in the screenshot) is updated with the name object, duplicated and placed at the root of the state (yellow in screenshot).
I would like to overwrite the properties of the pimRegistration(state).domain.patient object, not to create a new patient object.
The state update is called as shown below.
store.update((state) => ({
...state,
...patientPath, // state.domain.patient
...{ [property]: value },
}));
I have tried my different combinations without achieving the desired result.
The complete update function is shown below.
update(property: string, path: string, value: any) {
const paths: string[] = path.split(".");
const pathReducer = (state: IRegistrationState, path_: string) => {
if (paths.length <= 0) {
return state.domain;
}
return state[path_];
};
const domainPath = state.domain;
let patientPath, nokPath, referrerPath;
if (path.includes("patient")) {
patientPath = paths.reduce(pathReducer, state);
}
if (path.includes("nok")) {
nokPath = paths.reduce(pathReducer, state);
}
if (path.includes("referrer")) {
referrerPath = paths.reduce(pathReducer, state);
}
store.update((state) => ({
...state,
...patientPath,
...{ [property]: value },
}));
}
The function above is invoked with the following statement in Angular 2.
if (this.path.includes("patient")) {
this._repo.update("name", "domain.patient", this.name);
}
Thanks
Deep updates to a store can be tricky. In your function you seem to be spreading the updates at the root rather than at the level you want the update at. This answer here outlines the usual practice to update the state. In short, something like
const newState = {
...state,
domain: {
...state.domain,
patient: {
...state.domain.patient,
[property]: value
}
}
}
Dynamically passing a path and updating this state can be… cumbersome. There are libraries that can help you do it such as immer, but you can possibly hack your way around with normal JS/TS.

How to check action payload before state update?

I'm learning redux for my first react-redux application. How do I manage to verify payload value before changing my state ? For example the code below:
todoExample = {name: 'learn redux', author: 'myself'}
wrongTodoExample = {name: 'learn redux'}
dispatch(addTodos({todo: todoExample}))
dispatch(addTodos({todo: wrongTodoExample }))
With the above code, I add 2 todo items to my state but they don't have the same keys.
Is there a way to check the payload value in order to authorize the first addTodos but not the second one in my reducer?
I've searched on the internet but I couldn't find an answer. I'm sorry if my question is redundant.
You can use redux middleware to verify things, that is absolutely one of the intended use cases for middleware. Any middleware can inspect and modify any action going through the pipeline before it reaches the reducers, and even prevent an action from continuing on.
const verifyPayload = store => next => action => {
if (isVerifyPayload(action.payload)) {
return next(action);
} else {
return store.dispatch({ type: 'NOT_AUTHORIZED' })
}
}
const store = createStore(
initialState,
applyMiddleware(verifyPayload)
)
Not so clear about your description about same key, you mean name or author, or other specific keys like code\id.
You can try to validate your todos before dispatch or within the addTodos
function addTodos(payload) {
if (!payload.todo.code) return;
// simply return,
// otherwise throw an error to indicate that your todos miss a specific key
}
You can use a ternary operator in your reducer along with some util function to validate your todo. If the todo is valid, then transform your state to include the new todo, if not return the same state (effectively doing nothing).
const isValidTodo = (todo) => {
//Implement your validations. E.g: A valid todo will have a name and an author
return todo.name && todo.author;
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return isValidTodo(action.payload) ?
[
...state,
{
name: action.payload.name,
author: action.payload.text,
completed: false
}
]
: state
default:
return state
}
}
I've found a solution that suited well my needs and it's TypeScript. Now I have Payload Type wich allow me to define keys that I need in my action.payload without any validation function.
Thanks all for your asnwers.

Add item to an element of an array in Redux

I'm attempting to get my redux reducer to perform something like this:
.
however, I am outputting the following:
The following is the code I am using to attempt this. I've attempted to include action.userHoldings in the coinData array, however, that also ends up on a different line instead of within the same object. My objective is to get userHoldings within the 0th element of coinData similar to how userHoldings is in the portfolio array in the first image.
import * as actions from '../actions/fetch-portfolio';
const initialState = {
coinData: [],
userHoldings: ''
};
export default function portfolioReducer(state = initialState, action) {
if (action.type === actions.ADD_COIN_TO_PORTFOLIO) {
return Object.assign({}, state, {
coinData: [...state.coinData, action.cryptoData],
userHoldings: action.userHoldings
});
}
return state;
}
I guess you want to do something like this.
return Object.assign({}, state, {
coinData: [ {...state.coinData[0],cryptoData: action.cryptoDatauserHoldings: action.userHoldings}, ...state.coinData.slice(1) ],
});
}
slice(1) is to get all elements except 0th. For the first element, you can construct object the way you like. This should do the trick. Slice returns a new array unlike splice/push or others so safe to use in reducers. :)

How do you use `reselect` to memoize an array?

Suppose I have a redux store with this state structure:
{
items: {
"id1" : {
foo: "foo1",
bar: "bar1"
},
"id2": {
foo: "foo2",
bar: "bar2"
}
}
}
This store evolves by receiving full new values of items:
const reduceItems = function(items = {}, action) {
if (action.type === 'RECEIVE_ITEM') {
return {
...items,
[action.payload.id]: action.payload,
};
}
return items;
};
I want to display a Root view that renders a list of SubItem views, that only extract a part of the state.
For example the SubItem view only cares about the foos, and should get it:
function SubItem({ id, foo }) {
return <div key={id}>{foo}</div>
}
Since I only care about "subpart" of the states, that's what I want to pass to a "dumb" Root view:
const Root = function({ subitems }) {
// subitems[0] => { id: 'id1', foo: "foo1" }
// subitems[1] => { id; 'id2', foo : "foo2" }
const children = subitems.map(SubItem);
return <div>{children}</div>;
};
I can easily connect this component to subscribe to changes in the state:
function mapStatesToProps(state) {
return {
subitems: xxxSelectSubItems(state)
}
}
return connect(mapStatesToProps)(Root)
My fundamental problem is what happens when the part of the state that I don't care about (bar) changes.
Or even, when I receive a new value of an item, where neither foo nor bar has changed:
setInterval(() => {
store.dispatch({
type: 'RECEIVE_ITEM',
payload: {
id: 'id1',
foo: 'foo1',
bar: 'bar1',
},
});
}, 1000);
If I use the "naive" selector implementation:
// naive version
function toSubItem(id, item) {
const foo = item.foo;
return { id, foo };
}
function dumbSelectSubItems(state) {
const ids = Object.keys(state.items);
return ids.map(id => {
const item = state.items[id];
return toSubItem(id, item);
});
}
Then the list is a completely new object at every called, and my component gets rendered everytime, for nothing.
Of course, if I use a 'constant' selector, that always return the same list, since the connected component is pure, it is re-renderered (but that's just to illustrate connected components are pure):
// fully pure implementation
const SUBITEMS = [
{
id: 'id0',
foo: 'foo0',
},
];
function constSelectSubItems(state) {
return SUBITEMS;
}
Now this gets a bit tricky if I use an "almostConst" version where the List changes, but contains the same element.
const SUBITEM = {
id: 'id0',
foo: 'foo0',
};
function almostConstSelectSubItems(state) {
return [SUBITEM];
}
Now, predictably, since the list is different, even though the item inside is the same, the component gets rerendered every second.
This is where I though 'reselect' could help, but I'm wondering if I am not missing the point entirely. I can get reselect to behave using this:
const reselectSelectIds = (state, props) => Object.keys(state.items);
const reselectSelectItems = (state, props) => state.items;
const reselectSelectSubItems = createSelector([reSelectIds, reSelectItems], (ids, items) => {
return ids.map(id => toSubItem(id, items));
});
But then it behaves exactly like the naive version.
So:
is it pointless to try to memoize an array ?
can reselect handle this ?
should I change the organisation of the state ?
should I just implement shouldComponentUpdate on the Root, using a "deepEqual" test ?
should I give up on Root being a connected component, and make each LeafItems be connected components themselves ?
could immutable.js help ?
is it actually not an issue, because React is smart and will not repaint anything once the virtual-dom is computed ?
It's possible what I'm trying to do his meaningless, and hides an issue in my redux store, so feel free to state obvious errors.
You're definitely right about the new array references causing re-renders, and sort of on the right track with your selectors, but you do need to change your approach some.
Rather than having a selector that immediately returns Object.keys(state.item), you need to deal with the object itself:
const selectItems = state => state.items;
const selectSubItems = createSelector(
selectItems,
(items) => {
const ids = Object.keys(items);
return ids.map(id => toSubItem(id, items));
}
);
That way, the array will only get recalculated when the state.items object is replaced.
Beyond that, yes, you may also want to look at connecting your individual list item components so that each one looks up its own data by ID. See my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance for examples. I also have a bunch of related articles in the Redux Techniques#Selectors and Normalization and Performance#Redux Performance sections of my React/Redux links list.

Categories