I have a project, where I use react-redux, and I have a reducer, which by idea should add one element in array and return new array. How I can do this?
/*---- Reducer ----*/
case CHANGE_EVENT_USERS:
return { ...state, users: payload };
/*---- Here's my hopeless tryings ----*/
userClickHandler() {
const { id, homeFloor, avatarUrl, login } = this.props;
const user = { id, homeFloor, avatarUrl, login };
this.props.changeEventUsers([...user]); // []
this.props.changeEventUsers(this.props.event.users.push()); // number
}
I'm not sure if I understand you correctly but from my understanding the solution to your problem would look something like this:
case CHANGE_EVENT_USERS:
return { ...state, users: [ ...state.users, action.payload ] };
I like better the syntax of concat.
In your reducer do:
case CHANGE_EVENT_USERS:
return users.concat(action.payload);
Do the add directly in the reducer.
From your component
this.props.changeEventUsers(newUser); // add the new user
In the reducer
return { ...state, users: [...state.users, payload] };
I made the assumption that "payload" contains the info coming from the action and the users array is by default initialised with an empty array value []
Use concat()
const initialArray = [1, 2, 3];
const elemenToAdd = 4;
const newArray= initialArray.concat([elementToAdd]);
[1, 2, 3, 4]
Notice I'm using const here to emphasize that the initial array was not mutated.
The great thing about the method above, it's that it can be used to chain operations together.
result = initialArray.concat(..).filter(..).concat(..);
(where .. represents skipped code details)
You can also use concat by passing in arrays as parameters:
newArray = concat(initialArray, [elementToadd])
Or use es7 spread operator syntax for array concatenating:
newArray = [...initialArray, elementToAdd, ...[5], [6] ];
[1, 2, 3, 4, 5, [6] ]
Use ... to send individual elements of the supplied array to be elements in the new array; without the dots, the supplied array is itself the element.
So in the your case, the line in question could be written as:
I found my solution:
this.props.changeEventUsers([...this.props.event.users, user]);
Related
I have an array like this:
a=[{id:1 , operand:2 ,children:[{a:1]{b:2}]
And I do this when I want to add a new field:
const [items, setItems] = useState(a);
const gettableData = (value) => {
let operanList = items;
let repeatListError = tableData.filter((i) => i.operandId === value.operandId);
if (!repeatListError.length > 0) operanList.push(value);
setTableData(operanList);
};
This method is called every time I click a button.
I want to check operand when I add a new object, if there is only update children.like:
value=[{id:1 , operand:2 ,children:[{a:1]{b:2}{c:3}{d:4}]
I see some inconsistencies in the variable names used in your code, e.g. where is tableData? Is it the items which is part of state?
I don't understand if children is an array of objects or just an object with key value pairs.
Anyway, as I mentioned in my comment, the easiest way to achieve this is using map. Consider it to be a generic implementation and try to use it in your code.
const a=[{id:1 , operand:2 ,children:[{a:1]{b:2}] // What is the data stucture of children? Is it array of objects or just object?
const [items, setItems] = useState(a);
const gettableData = (value) => {
// creating a copy is not required as map will return a new array.
// map => filter + modify, so use map
let updatedList = tableData.map((i) => {
if (i.operandId === value.operandId) {
// match found, so update children
return {
...i, // first, copy everything from i
children: [ // then update children, and since children is an array of objects
...i.children, // first copy every key of i's children
...value.children // then copy value's children
]
}
}
return i; // no match? return i as it is.
});
setItems(updatedItems)
First reformat value like the following.What you have is incorrect in syntax:
const value=[{id:1 , operand:2 ,children:{a:1,b:2,c:3,d:4}}]
This how you can change the children inside the objects that are in your array:
const list = [
{ id: 1, operand: 2, children: { a: 1, b: 2, c: 3, d: 4 } },
{ id: 2, operand: 3, children: { a: 1, b: 2, c: 3, d: 4 } },
];
let newList = [...list];
let updatedChildren = { ...newList[1].children, e: 5, f: 6 };
let updatedItem = { ...newList[1], children: updatedChildren };
newList.splice(1, 1, updatedItem);
console.log(newList);
setState(newList);
you create a new instance of the array. update the children attribute of your desired item ( in this case i chose 1 ). then update the entire object that is in your array ( it contains the id & operand and children ). and finally mutate the newList with replacing the original item by the new one. In the splice method remember the second number must always be one, as it will remove one item and the first number given should be the index of the item that is needed to be changed ( in this case we chose one). then you can continue with setting the state as you wish since you have a new, updated list.
You have to put the array in another variable and tell it that if it has children, it will remember it in the children field and set a new value for it.
I have an array that I am trying to set the value for from a different array however my arrays are not similar the array I am trying to set should look like this
expanded = [id1, id2, id3]
while the array I am setting from looks more like this
data = [{id: 1, name:"test"},{id:2, name:"test2"},{id:3, name:"test3"}]
not sure how to do this with this.setState
const expanded = [1, 2, 3]
const data = expanded.map((e)=>(
{id: e, name: `test${e-1 ? e : '' }`}
))
// here you can use setState()
console.log(data);
const [array, setArray] = useState(data.map(ítem => item.id))
Or, if you are using clase components:
this.state = {
array: data.map(item => item.id)
}
First make your desired array from the second array. And then use that array in your state.
const data = [{id: 1, name:"test"},{id:2, name:"test2"},{id:3, name:"test3"}];
const desiredArray = data.map(item=> Object.keys(item)[0]+item.id );
console.log(desiredArray);
This code snippet will give you the array that you have mentioned you require in your project. Then setState on it accordingly.
I have this two states:
stateOne: [
'marca01',
'marca02'
]
stateTwo: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}
]
And I'm using lodash to merge both in a third state, but as you see the first state is an array and the second one is an array of objects so I can't merge these two like I want to...
Something like this:
stateThree: [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men',
MARK:'marca01'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman',
MARK:'marca02'
}
]
How can I get the desired result (is not necessary to use lodash)
Without lodash :
const state3 = state2.map((product, index) => ({
...product,
MARK: state1[index]
}));
Here .map returns a new array whose new values are returned by the anonymous function.
The spread operator ...product flattens the previous object properties in the new object. This syntaxic sugar can be replaced by Object.assign.
Then we add a new MARK property based on the other state with the same index.
I used the map method and the spread operator in order to prevent state mutation which can be harmful in React.
You could iterate over the second array, and add the properties.
let s1 = [
'marca01',
'marca02'
]
let s2 = [
{
PRODUCTO:'hat',
PRICE:1499,
CATEGORY:'Men'
},
{
PRODUCTO:'Shirt',
PRICE:1233,
CATEGORY:'Woman'
}]
s2.forEach((x,i) => x.MARK = s1[i])
console.log(s2)
You can do like this:
stateThree = stateTwo.map( (element, i) => ({...element, MARK: stateOne[i] } ))
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.
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.