Updating React state as a 2d array? - arrays

Given this 2d array React state,
this.state =
board: [
[null, null, null, null],
[null, {id: 21, canSelect: false}, null, null],
[null, {id: 42, canSelect: false}, null, null],
[null, null, null, null]
]
}
I have 3 main questions regarding using setState to update this state:
1) How would I target a specific index within this 2d array state in React? Something like board[1][1]: {newObject}?
2) How would I update only the "canSelect" values for each?
3) If there were an unknown number of array indexes I would need to update (say between 2 and 8), how would I update only those indexes?
Any help is really appreciated :)

1) How would I target a specific index within this 2d array state in React?
To access let's say, the object with id = 21 do that:
console.log(this.state.board[1][1].id)
2) How would I update only the "canSelect" values for each?
To change a specific canSelect property do it in a immutable way:
onChange = e => this.setState(state =>({
...state,
board : state.board.map((arr, i) => arr.map((item,j) =>{
if(!item.hasOwnProperty('canSelect') return item
return {...item, canSelect: !state.board[i][j]}
}))
}))
If there were an unknown number of array indexes I would need to update (say between 2 and 8), how would I update only those indexes?
If you want non-consecutive arrays(sparse), just create an Object and then map it's keys instead of indexes:
const obj = {myKey : 'foo'}
console.log(obj.myKey) //foo
See more about sparse arrays in this question, but the gist here is do not use it, even if they do not take up more space than a "normal" array, what you really want is a hashing mecanism that map key names to values, good old JSON
Update
Based on your comments I've realized that I misunderstood the third problem, but I'm not excluding it cause it can be useful.
So let's assume you want to update the canSelect property on every id contained in a list:
const idsToUpdate = [1,22]
But you don't know if the given ids exist on your current collection, the solution would be iterate through every item, check if they aren't null, then check if the id is inside the idsToUpdate list and only then update the canSelect property:
this.setState(state =>({
...state,
board : state.board.map((arr,i) => arr.map((item, j) =>{
if(!item) return item
if(!idsToUpdate.includes(item.id)) return item
return {...item, canSelect: true}
}))
}))

Related

Instead of replacing the object it adds a new one in a nested array with React

Sup,
So I have this object:
data: {
OtherFields: {},
Skills: [
{
id: Math.random(),
name: 'Default Category',
skills: [],
},
],
{
So the Skills Array is very dynamic, I need to add categories, and each categories have their own array named skills, that will be filled with other objects, and the default category is there.
While the skills inside will have:
{
id: Math.random(),
skillName: 'Default Category',
}
What I want to do is add the skill to the specific category in a dynamic way with the id category as we don't know how much the user will add.
Here what I did until now:
const handleAdd = (id, content) => {
// id is the cateogry of that specific cateogry that im receiving from input
// content is the value of the input
// this is the object i need to push into the category
const newItem = {
id: Math.random(),
skillName: content,
};
// and then update it,
const newData = data.Skills.find((i) => i.id === id);
console.log(newData)
newData.skills.push(newItem);
setData({ ...data, Skills: [...data.Skills, newData] });
//this it works but adds another cateogry and doesnt not replace the current one with the new value that is added
};
This appends newData to the array:
Skills: [...data.Skills, newData]
But it doesn't filter that same record from the array when appending it. It basically means "the whole array as-is, plus this new element". Even if that element is conceptually a duplicate, the code doesn't know that. (Heck, even if it's by reference a duplicate, there's still nothing stopping an array from containing two references to the same object.)
It sounds like you want to filter that whole array to remove that element before re-appending it. For example:
Skills: [...data.Skills.filter(s => s.id !== newData.id), newData]
Since you're modifying the original object this should work, rename your variables to make it easier to read.
Also consider not changing the original object.
setData({ ...data, Skills: [...data.Skills] });

Remove duplicate values from an array of objects in reactjs

I have an array like so:
reportData = [
{status: "Resolved", type: Placement of bins},
{status: "Assigned", type: Placement of bins},
{status: "Active", type: Sewerage}
]
Now this is what I am trying to do. I want to make as many checkboxes as the count of reportData array but also remove the duplicated values, like in this case, I want "Placement of bins" only once (one checkbox). So overall according to the array there should be only 2 checboxes, but I'm getting three because obviously there are 3 objects in the array.
{
reportData.map((obj) => {
return (
<FormControlLabel
value="type"
control={<Checkbox color="primary" />}
label={obj.type}
labelPlacement="end"
/>
)
})
}
Is there some way to first sort out the duplicated array of objects and maybe then I can map it?
Here is what I would do,
const mapped = reportData.map((obj, index) => obj.type);
const filtered = mapped.filter((type, index) => mapped.indexOf(type) === index )
Here is what happened, map function made a new array called mapped that has the all types you had in reportData including duplicates.
filter filtered the duplicates as; array.indexOf(element) returns index of the first element that matches. Here, 'Placement of bins' is in 0 and 1 indexes. So the iterations be like true false true since in first iteration it is 0 === 0, in second: it's 0 === 1 and in third it's 2===2. So filtered array only gets the elements of index 0 and 2
And then using the filtered array to map to get checkboxes.
You could use lodash method _.uniqBy, it is available in the current version of lodash 4.17.2.
Example:
_.uniqBy([{status:"Resolved", type:'Placement of bins'},
{status:"Assigned", type:'Placement of bins'},
{status:"Active", type:'Sewerage'}], 'type');
// => [{status: "Resolved", type: "Placement of bins"}, {status: "Active", type: "Sewerage"}]
More info: https://lodash.com/docs/#uniqBy

What is recommended way to determine if a Redux state empty array is the initial state or the result of an API call that returns an empty array?

Let say I have an initial state tree that looks like this:
{
users: [],
items: []
}
In some cases, the result of calling the items API endpoint may result in a state tree like this:
{
users: [],
items: [
{itemId: 100, itemName: "Something100"},
{itemId: 101, itemName: "Something101"}
]
}
In other cases where there are not items to display, the state tree after an API call will be identical to the initial state tree.
Now in my component I'm using useEffect, something like this:
useEffect(() => {
if (items.length === 0) {
actions.loadItems().catch((error) => {
alert("Loading items failed! " + error);
console.log(error);
});
}
}, [items , actions]);
In this particular case, the length of items will be 0 in two cases: initial state or in case there are no results. If the API returns zero items and items.length === 0, then the action to call the API is executed repeatedly.
We really need a way of knowing that the empty array is the initial state or not. Of course I could change the state tree to something like:
{
users: {isLoaded: false, records: []},
items: {isLoaded: false, records: []},
}
That is going to add a bunch of overhead and refactoring and may not be most efficient/effective, so can someone give me a recommendation?
Unfortunately you will need some way of tracking the initialisation. If the issue is having to refactor then you can pull out this initialisation state into a higher order in the object from what you suggested. This will avoid refactoring so much:
{
isUsersLoaded: false,
isItemsLoaded: false,
users: [],
items: []
}
Another alternative is to init like this and check if users !== null etc.:
{
users: null,
items: null
}

Iterate array containing custom index

I am doing a small project in Angular8 and i have an array which contains data of users as object. This array is provided to me by the client which i can not change.
[
{
id:045#71,
name:'Ahmad',
isActive: false
},
{
id:047#71,
name:'John',
isActive: false
},
{
id:048#71,
name:'Doe',
isActive: false
}
]
In this array i have id's as custom indexes because of which i am not able to iterate this array. I am getting undefined in console when i try to iterate the array . Is there a way to iterate array with custom indexes. I even tried forEach loop but it is not working as well.
The method i used:
usersArray = [
{
id:045#71,
name:'Ahmad',
isActive: false
},
{
id:047#71,
name:'John',
isActive: false
},
{
id:048#71,
name:'Doe',
isActive: false
}
];
ngOnInit() {
this.usersArray.forEach((user)=>{
console.log(user.id + ' - ' + user.name);
})
}
What you have is what we typically call a collection. You can access an item from the collection if you know its index. In most cases, the index is an integer greater or equal to zero. i.e. collection[0] will give you the first item.
You can use collection.find(el => el.id ==='045#71') to find an element in the collection, if it doesn't exist will return undefined.
To find the index of an item you can use collection.findIndex(el => el.id ==='045#71'), in this case it will return 0
If you want to use customer indexes you would have to convert the collection into an object that uses your custom indexes as keys. There's surprisingly also a way to use custom keys in an array but it's not common practice in JavaScript.

How to correctly update redux state in ReactJS reducer?

I have the following structure in my Redux data store:
{
filterData: {
22421: {
filterId: 22421,
selectedFilters: [
{
filterName: 'gender',
text: 'Male',
value: 'male'
},
{
filterName: 'gender',
text: 'female',
value: 'female'
}
]
}
22422: {
filterId: 22422,
selectedFilters: [
{
filterName: 'colour',
text: 'Blue',
value: 'blue'
},
{
filterName: 'animal',
text: 'sheep',
value: 'Sheep'
}
]
}
Can someone point me towards using the correct way to update the selectedFilters array without mutating the state directly? i.e. How can I add/remove elements in the selectedFilters array for a given filterId?
Generally it's done by using non mutating (ie. returning a new object, rather than modifying the existing one) operators and function:
spread operator (...) for objects and arrays (for additions and edits),
filtering, mapping and reduction for arrays (for edits and removals),
assigning for object (for edits and additions).
You have to do this on each level leading to the final one—where your change happens. In your case, if you want to change the selectedFilters on one of those objects you'll have to do something like that:
// Assuming you're inside a reducer function.
case SOME_ACTION:
// Returning the new state object, since there's a change inside.
return {
// Prepend old values of the state to this new object.
...state,
// Create a new value for the filters property,
// since—again—there's a change inside.
filterData: {
// Once again, copy all the old values of the filters property…
...state.filters,
// … and create a new value for the filter you want to edit.
// This one will be about removal of the filter.
22421: {
// Here we go again with the copy of the previous value.
...state.filters[22421],
// Since it's an array and we want to remove a value,
// the filter method will work the best.
selectedFilters:
state.filters[22421].selectedFilters.filter(
// Let's say you're removing a filter by its name and the name
// that needs to be removed comes from the action's payload.
selectedFilter => selectedFilter.name !== action.payload
)
},
// This one could be about addition of a new filter.
22422: {
...state.filters[22422],
// Spread works best for additions. It returns a new array
// with the old values being placed inside a new one.
selectedFilters: [
// You know the drill.
...state.filters[22422].selectedFilters,
// Add this new filter object to the new array of filters.
{
filterName: 'SomeName',
text: 'some text',
value: action.value // Let's say the value comes form the action.
}
]
},
}
}
This constant "copy old values" is required to make sure the values from nested objects are preserved, since the spread operator copies properties in a shallow manner.
const someObj = {a: {b: 10}, c: 20}
const modifiedObj = {...someObj, a: {d: 30}}
// modifiedObj is {a: {d: 30}, c: 20}, instead of
// {a: {b: 10, d: 30}, c: 20} if spread created a deep copy.
As you can see, this is a bit mundane to do. One solution to that problem would be to create some kind of nested reducers functions that will work on separate trees of the state. However, sometimes it's better not to reinvent the wheal and use tools that are already available that were made to solve those kind of problems. Like Immutable.js.
If you want to use a dedicated library for managing the immutable state (like suggested in another answer) take a look at Immer.
I find that this library is simpler to be used than Immutable.js (and the bundle size will be smaller too)

Resources