I have nested objects on my state:
constructor(props) {
super(props);
this.state = {
aba1Array: [
{
item:[
{
id: 1,
nome: "apple",
selected:false,
},
{
id: 2,
nome: "juice",
selected:false,
}
]
},
{
item:[
{
id: 3,
nome: "apple",
selected:false,
},
{
id: 4,
nome: "juice",
selected:false,
}
]
}
],
};
}
Now I want to make a copy of the array, update an item, and setState again (is this the correct way to update, right ?), how to do this ? this is my current code:
updateItem(id){
let newItems = Object.keys(this.state.aba1Array).map((subitem) => {
subitem.item.map((item) => item.id===id ? {...item, selected: true } : item);
});
this.setState({ aba1Array: newItems });
}
this is returning:
TypeError: undefined is not an object (evaluating 'subitem.item.map')
edited: The problem begun when I added second level of nesting , before it was working fine with just one level , like this :
let newItems = Object.keys(this.state.aba1Array).map((item) => {
item.id===id ? {...item, selected: true } : item);
edited2: in another part of the screen where I only list the items , i'm using:
listItem() {
return this.state.aba1Array.map((subitem)=> {
return ( subitem.item.map((item) => { return ( <View>
...
and it works fine. Why in the update function it gives me error ? I don't understand
Edited3: - SOLUTION -
My solution was to use library object-path-immutable:
updateItem(itemIndex,subIndex){
const newObj = immutable.update(this.state.aba1Array, [subIndex,'item', itemIndex , 'selected' ], v => true );
this.setState({ aba1Array: newObj });
}
My solution was to use library object-path-immutable:
updateItem(itemIndex,subIndex){
const newObj = immutable.update(this.state.aba1Array, [subIndex,'item', itemIndex , 'selected' ], v => true );
this.setState({ aba1Array: newObj });
}
Best solution :
I forgot to return the value on map function , so the new array wasnt getting the full tree of the object , the best solution is:
let newItems = this.state.aba1Array.map((subitem) => {
subitem.item.map((item) => {
item.selected = true;
return item;
})
return subitem;
});
Related
I have this state
this.state = {
dropdown1: false,
dropdown2: false,
dropdown3: false
}
I want to access to these dropdowns in state using this.setState but the number after 'dropdown' comes from API
onMaca = (ev) => {
this.setState({
dropdown + ev: true
})
}
So I want the key to be dynamic 'dropdown1' for example.
Thanks for your answers
you can access the object property like this object['property name']
onMaca = (ev) => {
this.state['dropdown' + ev]= true;
this.setState({
...this.state
})
}
https://codezup.com/add-dynamic-key-to-object-property-in-javascript/
You can use any of these to set key dynamically. I will try to update the answer with an example in a while for setState.
The state is a JS object, so you can get its keys as usual, like so:
const stateKeys = this.state.keys()
Now you have an array: [ "dropdown1", "dropdown1", "dropdown1" ]
One way to use it would be:
const keysMap = statekeys.map(( item, i ) => return {
key: item,
idx: i,
number: item.replace( /dropdown/, '' )
}
keysMap will look like so: [ { key: 'dropdown1', idx: 0, number "1" }, { key: 'dropdown1', idx: 1, number "2" }, { key: 'dropdown1', idx: 2, number "3" } ]
You can query keysMap for a given dropDownNumber like so:
let k = keysMap.find( kmap => kmap.key = dropDownNumber )
To set the dropdown's state:
this.setState({ k: <whatever> })
I'm trying to create new object with different properties name from Array.
Array is:
profiles: Array(1)
0:
column:
name: "profileName"
title: "Profile name"
status: "Active"
I want to create new function that return object with two properties:
id: 'profileName',
profileStatus: 'Active'
The function that I have create is returning only one property as undefined undefined=undefined.
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.reduce((obj, profile) => {
console.log('profiles', profile);
return ({
...obj,
id: profile.column.name,
profileStatus: profile.status,
});
}, {});
}
The function getProfile is taking as input array 'profiles' from outside,
I've just tested here and this seems to be working actually
const getProfile1 = (p) => p.reduce((obj, profile) =>({
...obj,
id: profile.column.name,
profileStatus: profile.status,
}), {});
You can use map as an alternative.
var profiles = [{"column":{"name": "profileName3","title": "3Profile name"},"status": "Active"},{"column":{"name": "profileName","title": "Profile name"},"status": "Active"}];
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.map(function(profile,v){
return {id:profile.column.name,profileStatus: profile.status};
});
}
console.log(getProfile(profiles));
Whenever I use reduce in this way, I usually index the final object by some sort of an id. As noted in another answer, you could use map in this situation as well. If you really want your final data structure to be an object, however, you could do something like this:
/**
* returns object indexed by profile id
*/
const formatProfiles = (profiles) => {
return profiles.reduce((obj, profile) => {
return {
...obj,
[profile.id]: {
id: profile.column.name,
profileStatus: profile.status,
}
};
}, {});
};
const profiles = [
{
id: 0,
status: 'active',
column: {
name: "profile_name_1",
title: "profile_title_1",
},
},
{
id: 1,
status: 'inactive',
column: {
name: "profile_name_2",
title: "profile_title_2",
}
}
];
const result = formatProfiles(profiles);
/**
* Result would look like this:
*/
// {
// '0': { id: 'profile_name_1', profileStatus: 'active' },
// '1': { id: 'profile_name_2', profileStatus: 'inactive' }
// }
In my reducer, I have the initial state which looks like this:
const initialState = {
isLoading: false,
events: [
{
year: 2021,
place: [
{
id: 1,
name: "BD"
},
{
id: 2,
name: "BD Test"
}
]
},
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
I have been trying to implement the functionality of deletion operation. So, when the button will be clicked the "deleteItems" action will be dispatched that will remove the corresponding items from the place. This functionality works fine. But,I am trying to remove the whole items from the events array if there is no values in place.
This is what I have tried already but it just removes the individual place. But, I need to write the logic here of removing the whole items when place becomes empty.
case "deleteItems":
return {
...state,
events: state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
})
};
So, after modifications, the state would look like this:(when there is no values in place for year 2021)
const initialState = {
isLoading: false,
events: [
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
Does anybody know how to accomplish this. Any helps would be highly appreciated.Thanks in Advance.
Demo can be seen from here
I removed the places first.
Then I filtered events based on whether the place array is empty or not.
After that, I returned the state.
case "deleteItems":
const eventsPostDeletingPlaces = state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
});
const eventsWithPlaces = eventsPostDeletingPlaces.filter((each) => each.place.length);
return {
...state,
events: eventsWithPlaces
}
Check the edited sandbox here
Basically the same logic as in the first answer, but with reduce instead of a map and an extra filter. Just an option.
case "deleteItems":
return {
...state,
events: state.events.reduce((events, event) => {
const place = event.place.find(x => x.id === action.id);
if (place) {
event.place = event.place.filter(x => x.id !== action.id);
}
if (event.place.length > 0) {
events.push(event);
}
return events;
}, [])
};
codesandbox
I’ve got a very deeply nested object in my React state. The aim is to change a value from a child node. The path to what node should be updated is already solved, and I use helper variables to access this path within my setState.
Anyway, I really struggle to do setState within this nested beast. I abstracted this problem in a codepen:
https://codesandbox.io/s/dazzling-villani-ddci9
In this example I want to change the child’s changed property of the child having the id def1234.
As mentioned the path is given: Fixed Path values: Members, skills and variable Path values: Unique Key 1 (coming from const currentGroupKey and both Array position in the data coming from const path
This is my state object:
constructor(props) {
super(props);
this.state = {
group:
{
"Unique Key 1": {
"Members": [
{
"name": "Jack",
"id": "1234",
"skills": [
{
"name": "programming",
"id": "13371234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "abc1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
},
{
"name": "Black",
"id": "5678",
"skills": [
{
"name": "programming",
"id": "14771234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "def1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
}
]
}
}
};
}
handleClick = () => {
const currentGroupKey = 'Unique Key 1';
const path = [1, 1];
// full path: [currentGroupKey, 'Members', path[0], 'skills', path[1]]
// result in: { name: "writing", id: "def1234", changed: "2019-08-28T19:25:46+02:00" }
// so far my approach (not working) also its objects only should be [] for arrays
this.setState(prevState => ({
group: {
...prevState.group,
[currentGroupKey]: {
...prevState.group[currentGroupKey],
Members: {
...prevState.group[currentGroupKey].Members,
[path[0]]: {
...prevState.group[currentGroupKey].Members[path[0]],
skills: {
...prevState.group[currentGroupKey].Members[path[0]].skills,
[path[1]]: {
...prevState.group[currentGroupKey].Members[path[0]].skills[
path[1]
],
changed: 'just now',
},
},
},
},
},
},
}));
};
render() {
return (
<div>
<p>{this.state.group}</p>
<button onClick={this.handleClick}>Change Time</button>
</div>
);
}
I would appreciate any help. I’m in struggle for 2 days already :/
Before using new dependencies and having to learn them you could write a helper function to deal with updating deeply nested values.
I use the following helper:
//helper to safely get properties
// get({hi},['hi','doesNotExist'],defaultValue)
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//returns new state passing get(state,statePath) to modifier
const reduceStatePath = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
//use example
const state = {
one: [
{ two: 22 },
{
three: {
four: 22,
},
},
],
};
const newState = reduceStatePath(
state,
//pass state.one[1],three.four to modifier function
['one', 1, 'three', 'four'],
//gets state.one[1].three.four and sets it in the
//new state with the return value
i => i + 1 // add one to state.one[0].three.four
);
console.log('new state', newState.one[1].three.four);
console.log('old state', state.one[1].three.four);
console.log(
'other keys are same:',
state.one[0] === newState.one[0]
);
If you need to update a deeply nested property inside of your state, you could use something like the set function from lodash, for example:
import set from 'lodash/set'
// ...
handleClick = () => {
const currentGroupKey = 'Unique Key';
const path = [1, 1];
let nextState = {...this.state}
// as rightly pointed by #HMR in the comments,
// use an array instead of string interpolation
// for a safer approach
set(
nextState,
["group", currentGroupKey, "Members", path[0], "skills", path[1], "changed"],
"just now"
);
this.setState(nextState)
}
This does the trick, but since set mutates the original object, make sure to make a copy with the object spread technique.
Also, in your CodeSandbox example, you set the group property inside of your state to a string. Make sure you take that JSON string and construct a proper JavaScript object with it so that you can use it in your state.
constructor(props) {
super(props)
this.setState = { group: JSON.parse(myState) }
}
Here's a working example:
CodeSandbox
I am deleting an one id in an array, how do I setState after filtering it here?
https://codesandbox.io/s/react-example-1m2qn
const Debtors = () => {
const debtors = [
{
id: 1,
name: "John",
relation: "friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "John" },
{ id: 2, date: 2014, amount: "2000", purpose: "john" }
]
},
,
{
id: 2,
name: "Jack",
relation: "Friend",
statement: [
{ id: 1, date: 2010, amount: "1000", purpose: "jack" },
{ id: 2, date: 2014, amount: "2000", purpose: "jack" }
]
}
];
const [newDebtors, setdebtors] = React.useState(debtors);
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
// How to set debtors here ?
// setdebtors({ ...newDebtors, statement[0]: newList });
console.log(newList)
// How to set debtors here ?
There's two problems:
1) You are iterating off the original debtors object in your render, instead of the newDebtors state you created via useState(), which is why there does not appear to be any UI change.
You need: newDebtors[0].statement.map
2) You need to pass in the item index in your handleDelete() so it knows what item in the array to update. You can have the function do something like this:
In the onClick:
<a
href="javascript:;"
onClick={() => handleDelete(stat, i, 0)}
>
In the handleDelete():
const handleDelete = (stat, i, arrayIndex) => {
const updatedDebtors = newDebtors.map((item, index) => {
if (index === arrayIndex) {
return {
...item,
statement: item.statement.filter(
statement => statement.id !== stat.id
)
};
} else {
return item;
}
});
setDebtors(updatedDebtors);
};
See sandbox for full solution: https://codesandbox.io/s/react-example-x7uoh
You should do it like that:
setdebtors((prevState) => {
let newArray = Array.from(prevState); // Copy the array
// Manipulate the array as you wish
return newArray; // return it
});
The problem is you are mutating the array of "debtors" you need to map through the array of debtors and change any properties in the object.
const handleDelete = (stat, i) => {
const newList = newDebtors.map((debtor, i) => {
if (i === 0) {
debtor.statement = debtor.statement.filter(x => x.id !== stat.id);
}
return debtor;
});
setdebtors(newList);};
An even better approach is to use "useReducer" which is used for mutating more complex pieces of state, like you have here. THe docs are very helpful useReducer
Hmm I dont know what exactly you are trying to do,
Is this what you are looking for?
const handleDelete = (stat, i) => {
const newList = newDebtors[0].statement.filter(x => x.id !== stat.id);
const newFirstItem = {...newDebtors[0],statement: newList}
const newDebtorList = newDebtors.filter(x => x.id !== newFirstItem.id);
newDebtorList.unshift(newFirstItem);
setdebtors(newDebtorList);
}
I know this seems complex but you kinda actually need to do this as you cannot mutate an array in the state...
What I did here is I first created a new statement list(newList), then created a newFirstItem to be set as the new newDebtors[0], then created a new array(newDebtorList) of all the elements of newDebtors except the first one, I modified this array by pushing the newFirstItem to the 0th position(using unshift)
Finally updated the state with this new array...
hope it helps :)
Note: this is for changing the 0th element if you have the id please change the code accordingly