Issue with Immutable Update Pattern in Redux - javascript

I am trying to update a nested array filed in redux reducer, here is my array i want to update ( just add updating:true filed ) my array looks like
I want to add a updating:true filed on room object after number filed
items: Array(1)
0:
name: (2) [{…}, {…}]
rooms: Array(2)
0: {_id: "5d494e5b11b962065632c760", number: "100" …}
1: {_id: "5d494e6211b962065632c761", number: "102" …}
look like
items :
[{
name: [{}],
rooms : [{}]
}]
Here is my redux reducer code, i am trying to update
case Constants.Status_ADD_REQUEST:
var ID = ID; // action id
return {...state,
items:[{...state.items,
rooms:[{...state.items.rooms,
rooms:{...state.items.rooms.map(x=> x._id === ID ? {...x,updating: true} : x )}}]
}]
};
anyway its not working :) please guide me to correct it

Have a second look at the spread Syntax in JavaScript, especially spread for array literals. For example in your reducer, you create a new state with an items array containing only one fix object (it would still be only one object, even if you would add more items in your example). This item object contains your previous converted and spread items array and rooms property - not really your state shape.
How you could create the state instead:
Full example repo (with TypeScript)
const initialState = {...}
function rootReducer(state = initialState, action) {
switch (action.type) {
case Constants.Status_ADD_REQUEST:
return {
...state,
items: state.items.map(item =>
item.rooms.some(i => i.id === action.id)
? {
...item,
rooms: item.rooms.map(room =>
room.id === action.id ? { ...room, updating: true } : room
)
}
: item
)
};
default:
return state;
}
}
in the style of "Updating an Item in an Array" Redux docs.
If you have to ensure immutable patterns this way and manually, things will get ugly fast. Reason: The key to updating nested data is that every level of nesting must be copied and updated appropriately (structural sharing).
To circumvent those problems, your goal should be to keep your state flattened and to compose reducers. If cloning your state still becomes too complex, you could make use of Immutable Update Utility Libraries. I personally can recommend immer, as you can change state in a mutable way, while the library takes care of deep clones/structural sharing via JavaScript proxies transparently.
PS: It could also make sense to think of another name for "update room", if it is some of your domain/app data.

Related

What do you suggest to make undo?

I use a series of data as follows:
[
{
name: 'name1',
background:'red',
child:[
{
name:'',
id:'',
color:'',
text:'',
border:''
},
{
name:'',
id:'',
color:'',
text:'',
border:''
}
]
},
{
name: 'name2',
background:'red',
child:[
{
name:'',
id:'',
color:'',
text:'',
border:''
},
{
name:'',
id:'',
color:'',
text:'',
border:''
}
]
}
]
I'm going to save all the changes to another variable, and I used a deep copy to do that, but when I log in, the variables are the same.I need to children all the children changes too.
I wrote it in Reducers
const Reducers =(state = initialState, action) => {
switch (action.type) {
case NEW_OBJECTS_PAST:
const OldPast = JSON.parse(JSON.stringify(state.past))
const newDate = JSON.parse(JSON.stringify(state.present))
// const newDate = _.cloneDeep(state.present);
const newPast = [
OldPast,
newDate
];
return {
...state,
past : _.cloneDeep(newPast) ,
}
case OBJECTS:
return {
...state,
present: action.objects,
// future:[]
}
Do you suggest another way to build undo in REACT and REDUX ?
I tried the libraries available for this, but got no answer.
First two remarks :
you should never deep clone parts of your state, it doesn't bring you any benefits as the state is immutable anyway, but is very detrimental for memory usage and for performance (both when doing the deep cloning and then when using the deep cloned state),
you should use Redux toolkit, which makes it way easier to write immutable reducers and prevent many errors.
To implement undo, I'm not sure what your actions are supposed to mean but you can do it as follows
the state contains state.present (the current state) and state.past (an array of past states)
whenever you do an action that you want to undo, you push the current state.present at the end of state.past and compute the new state.present
whenever you want to undo, you pop the last element of state.past and put it in state.present.
In your code I can't see any undo action, and you're also building nested arrays because of new Past = [oldPast, new Date], you most likely meant to spread oldPast.

Redux store is not updating when modifying element propriety of an array object

I have below code
case 'COMPLETE_TODO' :
state.todos[state.todos.findIndex((obj => obj.id == action.value))].status = "COMPLETED"
return {
...state,
todos: state.todos
}
I feel like an array is not taken as a modified array because just a property of one single element has been updated.
Any idea?
Thanks
Do not mutate state, make a copy of state and then perform operations on it
case 'COMPLETE_TODO' :
return {
...state,
todos: state.todos.map(obj=> ({...obj, status: obj.id == action.value ? "COMPLETED" : obj.status}))
}
map create a new array, ... spread syntax creates a shallow copy, if you object is deeper then one level, then you should do a deep clone,
For deep cloning you can use
let deepCopy = JSON.parse(JSON.stringify(state.todos))

Immer does not support setting non-numeric properties on arrays

I'm trying to update a piece of state with an array of data that I'm getting from the server. This is my reducer:
const schoolsDataReducer = (state = { data: [] }, action) =>
produce(state, draft => {
switch (action.type) {
case SET_INITIAL__DATA:
draft.data = [...action.payload.data]
break
}
})
I get this error:
"Immer does not support setting non-numeric properties on arrays: data"
How am I supposed to store an array of objects?
Are arrays in the state considered bad practice?
Am I missing something?
This happens when you pass something not an object for state. Make sure state is an object.

How to update only one specific property of nested object in associative array

I have associative array.
It's a key(number) and value(object).
I need to keep state of this array same as it is I just need to update one object property.
Example of array:
5678: {OrderId: 1, Title: "Example 1", Users: [{UserId: 1}, {UserId: 2}, {UserId: 3}]}
5679: {OrderId: 2, Title: "Example 2", Users: [{UserId: 1}, {UserId: 2}, {UserId: 3}]}
I need to update Users array property.
I tried this but it doesn't work:
ordersAssociativeArray: {
...state.ordersAssociativeArray,
[action.key]: {
...state.ordersAssociativeArray[action.key],
Users: action.updatedUsers
}
}
This is data inside reducer.
What I did wrong how to fix this?
Something that might help.
When I inspect values in chrome I check previous value and value after execution of my code above:
Before:
ordersAssociativeArray:Array(22) > 5678: Order {OrderId: ...}
After:
ordersAssociativeArray: > 5678: {OrderId: ...}
Solution (code in my reducer)
let temp = Object.assign([], state.ordersAssociativeArray);
temp[action.key].Users = action.updatedUsers;
return {
...state,
ordersAssociativeArray: temp
}
So this code is working fine.
But I still don't understand why? So I have solution but would like if someone can explain me why this way is working and first not?
If it could help here how I put objects in this associative array initialy:
ordersAssociativeArray[someID] = someObject // this object is created by 'new Order(par1, par2 etc.)'
What you are doing is correct, as demonstrated by this fiddle. There may be problem somewhere else in your code.
Something that I would recommend for you is to separate your reducer into two functions, ordersReducer and orderReducer. This way you will avoid the excessive use of dots, which may be what caused you to doubt the correctness of your code.
For example, something like:
const ordersReducer = (state, action) => {
const order = state[action.key]
return {
...state,
[action.key]: orderReducer(order, action)
}
}
const orderReducer = (state, action) => {
return {
...state,
Users: action.updatedUsers
}
}
I hope you find your bug!
Update
In your solution you use let temp = Object.assign([], state.ordersAssociativeArray);. This is fine, but I thought you should know that it is sometimes preferable to use a {} even when you are indexing by numbers.
Arrays in javascript aren't great for representing normalized data, because if an id is missing the js array will still have an undefined entry at that index. For example,
const orders = []
array[5000] = 1 // now my array has 4999 undefined entries
If you use an object with integer keys, on the other hand, you get nice tightly packed entries.
const orders = {}
orders[5000] = 1 // { 5000: 1 } no undefined entries
Here is an article about normalizing state shape in redux. Notice how they migrate from using an array in the original example, to an object with keys like users1.
The problem can be that you're using array in the state but in the reducer you're putting as object. Try doing:
ordersAssociativeArray: [ //an array and not an object
...state.ordersAssociativeArray,
[action.key]: {
...state.ordersAssociativeArray[action.key],
Users: action.updatedUsers
}
]
It will put ordersAssociative array in your state and not an object.

Two immutable lists - how to make triple equality work?

Let's say we have an immutable object that is created using Facebook's great Immutable.js. I want to compare two lists that were produced using .map or .filter out of single source and make sure they are equal. It seems to me, that when using map/filter you are creating a new object that has nothing to do with a previous object. How can I make triple equality === work? Does it make sense at all?
var list = Immutable.List([ 1, 2, 3 ]);
var list1 = list.map(function(item) { return item; })
var list2 = list.map(function(item) { return item; })
console.log("LIST1 =", list1.toJS()) // [1, 2, 3]
console.log("LIST2 =", list2.toJS()) // [1, 2, 3]
console.log("EQUAL ===?", list1===list2); // false! Why? How?
You can play with it here: http://jsfiddle.net/eo4v1npf/1/
Context
I am building application using React + Redux. My state has one list that contains items, that have attribute selected:
items: [
{id: 1, selected: true},
{id: 2, selected: false},
{id: 3, selected: false},
{id: 4, selected: true}
]
I want to pass only selected ids to another container, so I tried it using simple connect:
function getSelectedIds(items) {
return items
.filter((item) => item.get("selected"))
.map((item) => item.get("id"));
}
export default connect(
(state: any) => ({
ids: getSelectedIds(state.get("items"))
})
)(SomePlainComponent);
Problem is, if I set additional attributes:
{id: 1, selected: true, note: "The force is strong with this one"}
This causes state to change and SomePlainComponent to rerender, although the list of selected Ids is exactly the same. How do I make sure pure renderer works?
Edit with some additional info
For react pure rendering I was using mixin from react-pure-render:
export default function shouldPureComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
As it is not aware of props that could be immutable, they are treated as changed, i.e.
this.props = {
ids: ImmutableList1
}
nextProps = {
ids: ImmutableList2
}
Although both attributes ids are equal by content, they are completely different objects and do not pass ImmutableList1 === ImmutableList2 test and shouldComponentUpdate returns true. #Gavriel correctly pointed that deep equal would help, but that should be the last resort.
Anyway, I'll just apply accepted solution and problem will be solved, thanks guys! ;)
You can never have strict equality of immutable structures since an Immutable.js object, inherently, is unique.
You can use the .is function which takes two immutable objects and compares the values within them. This works because Immutable structures implement equals and hashCode.
var map1 = Immutable.Map({a:1, b:1, c:1});
var map2 = Immutable.Map({a:1, b:1, c:1});
console.log(Immutable.is(map1, map2));
// true
If you want to keep your component pure and working with === then you can also denormalize your Redux state and store the selectedIds as a property in the store. Only update this list when an action occurs that adds/removes a selected item or toggles an item selection, but not when other arbitrary properties of the item are updated.

Categories