how to properly update an array of Object in javascript/React - reactjs

I have here a map function for an array of object
and also added some condition
userList.map((item) => {
const newFilter = dailyData.filter((value) => {
return value.author == item.MSM;
});
let obj_idx = userList.findIndex(
(obj) => obj.MSM == newFilter[0]?.author
);
const newArr = userList?.map((obj, idx) => {
if (idx == obj_idx) {
return {
...obj,
storeTimeIn: newFilter[0]?.store,
timeIn: newFilter[0]?.date_posted,
storeTimeOut: newFilter[newFilter.length - 1]?.store,
timeOut: newFilter[newFilter.length - 1]?.date_posted,
};
} else {
return obj;
}
});
console.log(newArr);
setAttendanceData(newArr);
});
that just check if the Item exist in the array before updating it.
and this condition here works fine
if (idx == obj_idx) {
return {
...obj,
storeTimeIn: newFilter[0]?.store,
timeIn: newFilter[0]?.date_posted,
storeTimeOut: newFilter[newFilter.length - 1]?.store,
timeOut: newFilter[newFilter.length - 1]?.date_posted,
};
}
as seen in this picture
but when my condition becomes false the whole array of object becomes empty again
my hunch is I'm setting the state wrongly . which appear in the setAttendanceData(newArr)
this state is just an empty array state const [attendanceData, setAttendanceData] = useState([]);. is there a way to not update the whole array of object when the condition gets false like how can I use spread operator in this situation. TIA

I feel you need to add a little more description including the current react component and little bit about your inputs/outputs for a more refined answer.
userList.map((item, index, self) => {
const newFilter = dailyData.find((value) => {
return value.author === item.MSM;
});
// this is only needed if there can be multiple
// occurrence of same obj.MSM in 'userList' array
let obj_idx = self.findIndex((obj) => obj.MSM === newFilter[0] ? .author);
// why are you doing this
const newArr = self ? .map((obj, idx) => {
if (idx == obj_idx) {
return {
...obj,
storeTimeIn: newFilter[0] ? .store,
timeIn: newFilter[0] ? .date_posted,
storeTimeOut: newFilter[newFilter.length - 1] ? .store,
timeOut: newFilter[newFilter.length - 1] ? .date_posted,
};
} else {
return obj;
}
});
console.log(newArr);
// why are you doing this , updating state on every iteration
// after user.map attendanceData will only reflect for the last index in userList
setAttendanceData(newArr);
});
But if my assumptions are correct, this following code snippet may help you in some way.
setAttendanceData(
// make sure to run setAttendanceData only once
userList.map((item, index, self) => {
const newFilter = dailyData.find((value) => {
return value.author === item.MSM;
});
const obj_idx = self.findIndex((obj) => obj.MSM === newFilter[0] ? .author);
// there can be refinement around this but i feel this is what you need
if (index === obj_idx) {
return {
...obj,
storeTimeIn: newFilter[0] ? .store,
timeIn: newFilter[0] ? .date_posted,
storeTimeOut: newFilter[newFilter.length - 1] ? .store,
timeOut: newFilter[newFilter.length - 1] ? .date_posted,
};
} else {
return obj;
}
})
);

First, I would re-examine this logic. You're calling map inside of map on the same array, which is confusing. The top level map is used more like a forEach than a map because nothing is done with the output. You're also updating state on each iteration of the loop. I'm not sure how React handles this situation.
Try to only set state once the data is in the shape you want. Ideally it would only require one pass through the array. Here is an example:
const [attendanceData, setAttendanceData] = useState([]);
const [userList, setUserList] = useState([
{ MSM: "Edmond" },
{ MSM: "Dantes" },
{ MSM: "Conan" },
]);
const [dailyData, setDailyData] = useState([
{ author: "No one", store: "store", date_posted: "a date" },
{ author: "Edmond", store: "store", date_posted: "a date" },
{ author: "Conan", store: "store", date_posted: "a date" },
]);
useEffect(() => {
const mappedUserList = userList.map((user) => {
// Find the daily data author that matches the current user.
const day = dailyData.find((value) => {
return value.author == user.MSM;
});
if (!day) {
// If no author matched in daily data, return the user unchanged.
return user;
} else {
return {
...user,
storeTimeIn: day.store,
timeIn: day.date_posted,
// I'll leave these lines for you to figure out
// since I don't understand why you're getting the last element of the filtered data.
// storeTimeOut: filteredDailyData[filteredDailyData.length - 1]?.store,
// timeOut: filteredDailyData[filteredDailyData.length - 1]?.date_posted,
};
}
});
setAttendanceData(mappedUserList);
}, [userList, dailyData]);
console.log(attendanceData);

Related

Why the table data is not updated immediately after performing an action for the table

I have a table that has an action to delete..like this:
const deleteRow = (row) => {
let indexOfDeleted = -1;
let data = tableData;
data.forEach((item, index) => {
if (item.instrumentId === row.instrumentId) {
indexOfDeleted = index;
}
})
data.splice(indexOfDeleted, 1);
setTableData(data)
};
The data is deleted but I have to refresh it so that it is not displayed in the table.It does not seem to be rerender. What should I do?
for table:
const schema = {
columns: [
{
field: "persianCode",
title: "title",
},
],
operations: [
{
title: "delete",
icon: (
<DeleteIcon
className={clsx(classes.operationsIcon, classes.deleteIcon)}
/>
),
action: (row) => deleteRow(row),
tooltipColor: theme.palette.color.red,
}
],
};
You are mutating the state variable, in your deleteRow function. You should update the state with a copied array:
const deleteRow = (row) => {
setTableData(table => table.filter(data => data.instrumentId !== row.instrumentId))
};
Instead of finding the index and splicing it, you can just use the filter function. Since it returns a new array, we don't worry about mutating the state variable!
you will have to use Spread operator to reflect changes in react dom..
const deleteRow = (row) => {
let indexOfDeleted = -1;
let data = tableData;
data.forEach((item, index) => {
if (item.instrumentId === row.instrumentId) {
indexOfDeleted = index;
}
})
data.splice(indexOfDeleted, 1);
setTableData([...data]) /// like this
};

REACT UPDATE A DATA

I have a problem trying to update an Array of Objects that lives in a Themecontext, my problem is with mutation, I'm using Update from Immutability helpers. the thing is that when I update my array in my specific element, This appears at the end of my object.
This is my code:
function changeValueOfReference(id, ref, newValue) {
const namevalue = ref === 'colors.primary' ? newValue : '#';
console.warn(id);
const data = editor;
const commentIndex = data.findIndex(function(c) {
return c.id === id;
});
const updatedComment = update(data[commentIndex], {styles: { value: {$set: namevalue} } })
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
setEditor(newData);
this is my result:
NOTE: before I tried to implement the following code, but this mutates the final array and break down my test:
setEditor( prevState => (
prevState.map( propStyle => propStyle.styles.map( eachItem => eachItem.ref === ref ? {...eachItem, value: namevalue}: eachItem ))
))
Well, I finally understood the issue:
1 - commentIndex always referenced to 0
The solution that worked fine for me:
1 - Find the index for the Parent
2 - Find the index for the child
3 - Add an array []
styles : { value: {$set: namevalue} } => styles :[ { value: [{$set: namevalue}] } ]
Any other approach is Wellcome
Complete Code :
function changeValueOfReference(id, referenceName, newValue) {
const data = [...editor];
const elemIndex = data.findIndex((res) => res.id === id);
const indexItems = data
.filter((res) => res.id === id)
.map((re) => re.styles.findIndex((fil) => fil.ref === referenceName));
const updateItem = update(data[elemIndex], {
styles: {
[indexItems]: {
value: { $set: namevalue },
variableref: { $set: [''] },
},
},
});
const newData = update(data, {
$splice: [[elemIndex, 1, updateItem]],
});
setEditor(newData);
}

Recursive function in Reactjs Hooks?

I want to update the state using react Hooks useState(); ?
Here is an example :
I have global state on top of the app:
const [familyTree, setFamilyTree] = useState([
{
fam_id: 1,
name: "No name",
attributes: {
"Key1": "*",
"Key2": "*",
},
children: [
{
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "*",
"Key2": "*",
},
},
],
},
]);
I have a current object to update the global state:
let res = {
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "Update this",
"Key2": "*",
},
},
Recursive function in this case helps me to update global state with matched ID, but I have problem now,
const matchAndUpdate = (updater, target) => {
if (updater.fam_id === target.fam_id) {
target.name = updater.name;
target.attributes = updater.attributes;
}
if ("children" in target && Array.isArray(target.children)) {
target.children.forEach((child) => {
matchAndUpdate(updater, child);
});
}
};
familyTree.forEach((g) => {
matchAndUpdate(res, g);
setFamilyTree({ ...g }); // here is my try, this works on start, but on secound update i got error about forEach is not a function...
});
I don't know where to update state on correct way?
Thanks, o/
Because you update state inside of forEach().
Maybe you should use .map and update state then at the end of check array.
This is the solution:
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater, _child.children) : null
};
} else {
return {..._child,children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater,_child.children) : null};
}
});
};
This will return and array of children, so you will begin from the initial array:
const finalFamily = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
finalFamily will be the final updated array.
You can update the state like this:
// Option 1:
setFamilyTree(matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
// Option 2:
const newFamilyTree = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
setFamilyTree(newFamily);
--- NEXT QUESTION-- -
I understand that you want to create a method to push new children to child specified by id.
I developed a method that maintains attributes and old children:
const addChildrenToChild = (parent,numChildren) => {
const arrayChildren = [];
for (let i = 0; i < numChildren; i++) {
arrayChildren.push({
fam_id: Math.floor(Math.random() * 100),
name: "No name",
attributes: {
key1:"",
key2:""
},
});
}
return {...parent,children:parent.children && Array.isArray(parent.children) ? parent.children.concat(arrayChildren) : arrayChildren }
}
And upgrade matchAndUpdate to maintains old children
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: updater.children
//Filter updater children
.filter(_childFiltered =>
_child.children && Array.isArray(_child.children) ?
//check if exists new child in old children
_child.children.some(
_childToCheck => _childToCheck.fam_id !== _childFiltered.fam_id
) : true
)
//concat old children and check to update
.concat(
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
)
};
} else {
return {
..._child,
children:
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
};
}
});
};
And now. You can use the other method at the same time to add new children:
// Now we are going to add new children to the first element in familyTree array, and maintains old children if it has.
const newFamilyTree = matchAndUpdate(
addChildrenToChild(familyTree[0], 10),
familyTree
);
setFamilyTree(newFamilyTree);

typescript Promise - persist change in mapped array of objects

I am trying to replace the value (file paths) of the key/value entries in an array of objects upon the if-condition, that a file/ or files exist in the file directory Documents ( ios capacitor ionic ); else, just return the array unchanged.
Arrays
const currentItems = this.data;
const filenames = [val, val, ...];
// for loop
for (let filename of filenames) {
// capacitor FileSystem API; promise
Plugins.Filesystem.stat({
path:filename+'.jpeg',
directory: FilesystemDirectory.Documents
}).then((result) => {
// return path to file in Documents directory ( simplified)
const result.uri = this.imagepath;
// map array
const newItems = this.currentItems.map(e => {
// if entries match set the value for key 'linethree'
if (e.lineone === filename) {
return {
...e,
linethree: this.imagepath
}
}
// else, return e unchanged
else
return { ...e,}
});
}).catch( reason => {
console.error( 'onRejected : ' + reason );
})
}
The problem:
on every iteration - filename of filenames - the original array is mapped again - with its original values; thus each iteration overwrites the change from the previous iteration.
How can it be achieved that the value entry at key 'linethree' for each match - e.lineone === filename - persists ?
Desired replacement:
const filenames = ["uncle"];
[{"lineone":"nagybácsi","linetwo":"uncle","linethree":"./assets/imgs/logo.png"}]
[{"lineone":"nagybácsi","linetwo":"uncle","linethree":"_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg"}]
If i understand well you need something like this:
Solution with Array#Filter, Array#Some and Array#Map
const wantedImagePath = '_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg';
const fileNames = ["uncle"];
const someData = [
{
"lineone":"ikertestvérek; ikrek",
"linetwo":"twins",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nagybácsi",
"linetwo":"uncle",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nőtlen (man)",
"linetwo":"unmarried",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone": "bar",
"linetwo": "foo",
"linethree": "./some/demo/path/logo.png"
}
];
const modifed = someData.filter(x => fileNames.some(y => y === x.linetwo)).map(z => ({ ...z, linethree: wantedImagePath }));
console.log(modifed)
Update:
Solution if you want to keep current data and modify matched:
const wantedImagePath = '_capacitor_/var/mobile/Containers/Data/Application/D95D4DEF-A933-43F1-8507-4258475E1414/Documents/nagybácsi.jpeg';
const fileNames = ["uncle"];
const someData = [
{
"lineone":"ikertestvérek; ikrek",
"linetwo":"twins",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nagybácsi",
"linetwo":"uncle",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone":"nőtlen (man)",
"linetwo":"unmarried",
"linethree":"./assets/imgs/logo.png"
},
{
"lineone": "bar",
"linetwo": "foo",
"linethree": "./some/demo/path/logo.png"
}
];
const modified = someData.map(x => {
let match = fileNames.find(y => x.linetwo === y);
return match !== undefined ? ({ ...x, linethree: wantedImagePath }) : x;
});
console.log(modified)

Filter out existing objects in an array of objects

I don't think this is difficult, I just can't figure out the best way to do it. This function is creating an array, from a group of checkboxes. I then want to break up the array and create an array of objects, because each object can have corresponding data. How do I filter out existing rolesInterestedIn.roleType.
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray;
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
}
this.setState({ typeOfWork: newSelectionArray }, function() {
this.state.typeOfWork.map((type) => {
this.setState({
rolesInterestedIn: this.state.rolesInterestedIn.concat([
{
roleType: type,
}
])
}, function() {
console.log(this.state.rolesInterestedIn);
});
})
});
}
UDPATE
rolesInterestedIn: [
{
roleType: '',
experienceYears: ''
}
],
Because each time you do setState you are concatenating the new value to the prev one in rolesInterestedIn array. Add new value only when you are adding new item, otherwise remove the object from both the state variable typeOfWork and rolesInterestedIn.
Try this:
handleTypeOfWorkSelection(event) {
const newSelection = event.target.value;
let newSelectionArray, rolesInterestedIn = this.state.rolesInterestedIn.slice(0);
if(this.state.typeOfWork.indexOf(newSelection) > -1) {
newSelectionArray = this.state.typeOfWork.filter(s => s !== newSelection);
rolesInterestedIn = rolesInterestedIn.filter(s => s.roleType !== newSelection)
} else {
newSelectionArray = [...this.state.typeOfWork, newSelection];
rolesInterestedIn = newSelectionArray.map((workType) => {
return {
roleType: workType,
experienceYears: '',
}
});
}
this.setState({
typeOfWork: newSelectionArray,
rolesInterestedIn: rolesInterestedIn
});
}
Suggestion: Don't use multiple setState within a function, do all the calculation then use setState once to update all the values in the last.

Resources