Remove an element to a state list in react - javascript

I have the following variable on my state:
this.state = {
playerList: {
player: [
{
playerAlias: [
{
name: null
}
],
idPlayer: null,
playerName: null,
broadcastChannel: null,
clusterName: null
}
]
}
}
what I wanted to do is to delete a certain item on that same list,and for that I wrote this method:
delete = (player) => {
let listAux = this.state.playerList.player;
let newList = [];
listAux.map((playerAux) => {
if (playerAux != player) {
newList.push(playerAux)
}
})
this.setState({
playerList:newList
})
}
I though that this would work but it does not, the following error appear when trying to iterate all my elements of my variable on the render method:
Cannot read map of undefined, when trying to execute this for cycle this.state.playerList.player.map((player)

A few things:
Array#map is designed to create a new array with your modified values. Array#forEach is the appropriate method to use in this situation. But in this case you shouldn't be using either, because:
You're essentially reinventing Array#filter. Think of filter as doing the same operation with the exact opposite logic: Only keep values in my existing array if playerAux != player
const newList = this.state.playerList.player.filter((playerAux) => {
return playerAux != player
})
this.setState({ playerList: newList })
EDIT: Keep in mind what playerAux is. It's actually the object itself:
{
playerAlias: [
{
name: null
}
],
idPlayer: null,
playerName: null,
broadcastChannel: null,
clusterName: null
}
I not 100% sure what you're trying to compare, but it's probably playerName, which would make it playerAux.playerName != player

Related

Javascript: Recursion returning undefined

Why is my recursion returning undefined? I'm trying to "decode" nested children data from mongo which is returned as IDs like:
{
"_id": "613fd030f374cb62f8f91557",
"children": [
"613fd035f374cb62f8f9155b",
"613fd136f374cb62f8f91564",
"613fd1a5f374cb62f8f91571",
"613fd20bf374cb62f8f9157c"
],
...more data
}
My goal is to drill down and convert each child ID to the Object the ID represensents and convert their child IDs to objects then keep going until the child === [] (no children). I'm trying to have the initial parent (613fd030f374cb62f8f91557) have access to all multi-level nested children objects.
This is my code:
const get_documents = (documents) => {
// Loop through each document
documents.map((document) => {
if (document.parent === null) {
//convert children ids (_id) to array of objects
let dbData = [];
document.children.map((id) => {
let dbChildren = documents.find((x) => x._id === id);
dbData.push(dbChildren);
});
let formattedData = [];
dbData.map((child) => {
let formattedObject = {
id: child._id,
name: child.name,
depth: 0,
parent: child.parent,
closed: true,
children: child_recursion(child.children),
};
formattedData.push(formattedObject)
});
}
});
};
const child_recursion = (arr) => {
let dbData = [];
arr.map((id) => {
let dbChildren = documents.find((x) => x._id === id);
dbData.push(dbChildren);
});
let formattedData = [];
dbData.map((child) => {
let newChild = [];
if (child.children.length > 1) {
newChild = child_recursion(child.children);
}
let formattedObject = {
id: child._id,
name: child.name,
depth: 0,
parent: child.parent,
closed: true,
children: newChild,
};
formattedData.push(formattedObject);
if (newChild === []) {
return formattedData
}
});
};
What am I doing wrong in my recursion? Thank you for the help!
What is getting you here is mixing mutation with recursion which tends to make things a lot more messy.
What this line is telling me:
children: child_recursion(child.children),
is that you are always expecting child_recursion to return an array of formatted children.
However, in child_recursion you aren't always returning something. Sometimes you are mutating sometimes instead. Personally, I believe that it tends to be easier to wrap my head around not using mutation.
The process, therefore, should go something like this:
given an object
check if that object has children
if it does convert the children using this function
if it does not, stop recursion
return a new object, created from the input object with my children set to the output of the conversion.
In this way we can convert each child into an object with its children converted and so on.
Also it is somewhat strange that you are trying to convert all documents at once. Instead, as you gave in your question, you should focus on the object you are trying to convert and work downwards from there. If it is the case where objects can be both parents and children then you have a graph, not a tree and recursion would have to be handled differently than you are expecting.
We don't really need two functions to do this, just one and in the case where you already have the objects you are searching you can pass that along as well (if you don't just remove documents and get them from the db or some service instead). We can also use what is called an accumulator to set initial values before our recursion and track them as we recur.
const convert_children = (obj, documents) => {
const convert_children_acc = (obj, documents, parent, depth) => {
let partial_format = {
id: obj._id,
name: obj.name,
depth: depth,
parent: parent,
close: true
}
if (obj.children && obj.children.length === 0) {
return {
...partial_format,
children: []
}
} else {
return {
...partial_format,
children: obj.children.map(child => {
child = documents.find(x => child === x._id);
return convert_children_acc(child, documents, obj._id, depth+1)
})
}
}
}
return convert_children_acc(obj, documents, null, 0);
};
https://jsfiddle.net/5gaLw1y7/

How do I update the value of an object property inside of an array in React state

I cannot seem to find an answer on here that is relevant to this scenario.
I have my state in my React component:
this.state = {
clubs: [
{
teamId: null,
teamName: null,
teamCrest: null,
gamesPlayed: []
}
]
}
I receive some data through API request and I update only some of the state, like this:
this.setState((currentState) => {
return {
clubs: currentState.clubs.concat([{
teamId: team.id,
teamName: team.shortName,
teamCrest: team.crestUrl
}]),
}
});
Later on I want to modify the state value of one of the properties values - the gamesPlayed value.
How do I go about doing this?
If I apply the same method as above it just adds extra objects in to the array, I can't seem to target that specific objects property.
I am aiming to maintain the objects in the clubs array, but modify the gamesPlayed property.
Essentially I want to do something like:
clubs: currentState.clubs[ index ].gamesPlayed = 'something';
But this doesn't work and I am not sure why.
Cus you are using concat() function which add new item in array.
You can use findIndex to find the index in the array of the objects and replace it as required:
Solution:
this.setState((currentState) => {
var foundIndex = currentState.clubs.findIndex(x => x.id == team.id);
currentState.clubs[foundIndex] = team;
return clubs: currentState.clubs
});
I would change how your state is structured. As teamId is unique in the array, I would change it to an object.
clubs = {
teamId: {
teamName,
teamCrest,
gamesPlayed
}
}
You can then update your state like this:
addClub(team) {
this.setState(prevState => ({
clubs: {
[team.id]: {
teamName: team.shortName,
teamCrest: teamCrestUrl
},
...prevState.clubs
}
}));
}
updateClub(teamId, gamesPlayed) {
this.setState(prevState => ({
clubs: {
[teamId]: {
...prevState.clubs[teamId],
gamesPlayed: gamesPlayed
},
...prevState.clubs
}
}));
}
This avoids having to find through the array for the team. You can just select it from the object.
You can convert it back into an array as needed, like this:
Object.keys(clubs).map(key => ({
teamId: key,
...teams[key]
}))
The way I approach this is JSON.parse && JSON.stringify to make a deep copy of the part of state I want to change, make the changes with that copy and update the state from there.
The only drawback from using JSON is that you do not copy functions and references, keep that in mind.
For your example, to modify the gamesPlayed property:
let newClubs = JSON.parse(JSON.stringify(this.state.clubs))
newClubs.find(x => x.id === team.id).gamesPlayed.concat([gamesPlayedData])
this.setState({clubs: newClubs})
I am assuming you want to append new gamesPlayedData each time from your API where you are given a team.id along with that data.

Updating state as a nested object

I have this type of state in my app
state = {
abc: true,
array: [
{ id: 12345, done: false },
{ id: 10203, done: false },
{ id: 54321, done: false }
]
};
I am looking for a solution to the following problem: I need to change done property accordingly to passed id like in the following function when something like this handle(12345) is passed as an argument to handle function :
handle = id => {
this.state.array.map(e => {
if (e.key === id) {
this.setState({array: [
{ id: id, done: true },
{ id: 10203, done: false },
{ id: 54321, done: false }
]})
}
});
};
In simple words I need to change just one object in array based on provided id.
Thanks for any help or tips!
I'd write the handle method as:
handle = id => {
this.setState(prevState => {
const { array } = prevState;
return {
array: [
...array.filter(o => o.id !== id),
{id, done: true}
]
};
});
};
The idea here is that, remove the matching id from old state, and then add a new object to the array with id and done property as {id, done: true}.
Once you are allowed to restructure state to be hashmap instead of array:
state = {
abc: true,
array: {
12345: { done: false },
10203: { done: false },
54321: { done: false }
]
};
then you will be able to use power of spread operator:
let id = 12345;
this.setState({
array: {
...this.state.array,
[id]: {
...this.state.array[id],
done: true
}
}
});
Otherwise using array.map() seems to be the only option
You can use this Redux pattern to return a new array with the element in question being changed, while keeping your array immutable:
handle = id => {
const updatedArray = this.state.array.map(e => {
if (e.key === id) {
return { id: id, done: true };
}
else {
return e;
}
});
this.setState({array: updatedArray});
};
This keeps your data structures immutable while changing only what you need to change.
var newArray = this.state.array;
for(var i = 0; i < newArray.length; i++){
if(newArray[i].id === 12345) {
newArray[i].done = true;
}
}
this.setState({array: newArray});
By creating the newArray here you will be avoiding directly touching the state element, so you can change anything you want inside it afterwards you can set the state.

How to set value of an immutable state in Javascript?

Given an immutable state like this:
alerts: {
5a8c76171bbb57b2950000c4: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 1
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
and an object like this:
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
}
I want to replace the object with the same id in the alerts state (immutable), such that end result looks like this:
alerts: {
5a12356ws13tch: [
{
_id:5af7c8652552070000000064
device_id:5a8c76171bbb57b2950000c4
count: 2
},
{
_id:5af7c8722552070000000068
device_id:5a8c76171bbb57b2950000c4
count: 2
}
]
}
How can I do that? With mergeDeep, getIn, setIn, and updateIn, found on List, Map or OrderedMap ?
I tried doing something like this.. where index is 0 and deviceId is 5a12356ws13tch
Does not work though.
export const oneAlertFetched = (state, {deviceId, index, alert}) => state.setIn(['alerts', deviceId, index], alert).merge({fetching: false})
I tried this as well. Does not work.
export const oneAlertFetched = (state, {deviceId, index, alert}) => {
const a = state.alerts[deviceId][index]
state.alerts[deviceId][index] = Object.assign({}, a, alert)
return
}
By immutable, you mean that your property is non-writable.
If you want to modify your object in-place (not recommended), you will need the property to be at least configurable:
const device = alerts['5a12356ws13tch'][0];
if (Object.getOwnPropertyDescriptor(device, 'count').configurable) {
// Manually make it `writable`
Object.defineProperty(device, 'count', {
writable: true
});
// Update property's value
device.count++;
// Set it back to `non-writable`
Object.defineProperty(device, 'count', {
writable: false
});
}
console.log(device.count); // 2
If it is not configurable (cannot make it writable), or you do not want to jeopardize your application (it must be non-writable on purpose), then you should work on copies.
const device = alerts['5a12356ws13tch'][0];
alerts['5a12356ws13tch'][0] = Object.assign({}, device, {count: device.count + 1});
Object.assign() works on flat objects. If you need deep copy, have a look at my SO answer there.
I think you mean you want to return a new object with the updated payload?
function getNextAlerts(alerts, parentDeviceId, payload) {
const alertsForDevice = alerts[parentDeviceId];
if (!alertsForDevice || alertsForDevice.length === 0) {
console.log('No alerts for device', deviceId);
return;
}
return {
...alerts,
[parentDeviceId]: alerts[parentDeviceId].map(item =>
item._id === payload._id ? payload : item
),
}
}
const alerts = {
'5a12356ws13tch': [
{
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 1
},
{
_id: '5af7c8722552070000000068',
device_id: '5a8c76171bbb57b2950000c4',
count: 2
}
]
};
const nextAlerts = getNextAlerts(alerts, '5a12356ws13tch', {
_id: '5af7c8652552070000000064',
device_id: '5a8c76171bbb57b2950000c4',
count: 2,
});
console.log('nextAlerts:', nextAlerts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
If you're working with plain JavaScript objects and want to keep "immutable" approach you have to use spreads all over the nested structure of state object.
But, there are some tools already targeting this issue - lenses.
Here is the example of both approaches, array/object spreads and lenses - ramda repl.
In short, your example via spreads:
const oneAlertFetched = (state, { deviceId, index, alert }) => ({
...state,
alerts: {
...state.alerts,
[deviceId]: [
...state.alerts[deviceId].slice(0, index),
{ ...state.alerts[deviceId][index], ...alert },
...state.alerts[deviceId].slice(index + 1)
],
}
})
And via lenses using Ramda's over, lensPath, merge and __*:
const oneAlertFetched = (state, { deviceId, index, alert }) =>
R.over(
R.lensPath(['alerts', deviceId, index]),
R.merge(R.__, alert),
state
)
* R.__ placeholder used to swap 1st & 2nd parameters of R.merge
PS: lenses solution is intentionally adjusted to match the declaration of your function, so you can easily compare two approaches. However, in real life, with such powerful and flexible tool, we can rewrite the function to be more readable, reusable, and performant.

React - updating an object array in the state with setState

I'm working on a table planner app where guests can be assigned to dinner tables.
I have created an object array in the state called tabledata, which will contain objects like so:
this.state = {
tabledata: [
{
name: "Top Table",
guests: ["guest1", "guest2", "guest3"]
},
{
name: "Table One",
guests: ["guest3", "guest4", "guest5"]
}
]
}
I am then creating a drag and drop interface where guests can move between tables. I have attempted to update the state like so:
updateTableList (tablename, guest) {
const selectedTableObj = this.state.tabledata.filter((tableObj) => tableObj.name === tablename);
const otherTableObjs = this.state.tabledata.filter((tableObj) => tableObj.name !== tablename);
selectedTableObj[0].guests.push(guest);
const updatedObjectArray = [...otherTableObjs, selectedTableObj];
this.setState({
tabledata: [...otherTableObjs, ...selectedTableObj]
});
}
This works but because I am removing selectedTableObj from the state and then adding it to the end of the array I'm getting some funky results on screen. The updated table always goes to the bottom of the page (as you'd expect).
How can I update the object without changing its position within the array?
Find the index of the table you want to update using Array.findIndex(). Create a new tabledata array. Use Array.slice() to get the items before and after the updated table, and spread them into the new tabledata array. Create a new table object using object spread, add the updated guests array, and add the table object between the previous items:
Code (not tested):
updateTableList(tablename, guest) {
this.setState((prevState) => {
const tableData = prevState.tabledata;
const selectedTableIndex = tableData.findIndex((tableObj) => tableObj.name === tablename);
const updatedTable = tableData[selectedTableIndex];
return {
tabledata: [
...prevState.tabledata.slice(0, selectedTableIndex),
{
...updatedTable,
guests: [...updatedTable.guests, guest]
},
...prevState.tabledata.slice(selectedTableIndex + 1)
]
};
});
}
selectedTableObj[0].guests.push(guest) directly mutates the state which is not encouraged in React.
Try this:
this.setState((prevState) => {
const newData = [...prevState.tabledata];
// if you pass in `index` as argument instead of `tablename` then this will not be needed
const index = prevState.tabledata.findIndex(table => tableObj.name === tablename);
newData[index] = {
...newData[index],
guests: newData[index].guests.concat([guest]),
};
return { tabledata: newData };
});
You also did not remove the guest from its previous table so you need to modify for that.
You can do it with a Array.reduce
let newState = this.state
// let newState = {...this.state} // in case you want everything immutable
newState.tableData = newState.tableData.reduce((acc, table) =>
if(table.name === tableName) {
return acc.concat({...table, guests: table.guests.concat(newGuest)})
} else {
return acc.concat(table)
}
)

Categories