I have an array of objects. In that array, I want to update a single object, and in that object, I want to update only specific properties. I tried:
setRequiredFields(prevRequiredFields => ([
...prevRequiredFields, prevRequiredFields.find(x => x.name = field) ? {
isValid: isValid,
content: content,
isUnchanged: false
}
]));
But it didn't work. Required fileds is an array with the following structure:
[{
name: "Name",
isValid: false,
content: "",
isUnchanged: true,
tip: "Name cannot be empty",
isValidCondition: notEmptyRegex,
reportState: validateField
}]
What I'm trying to do here is to update only a isValid, content and isUnchanged of one specific object inside that array. How can I accomplish that?
If you have an array of objects, and you want to update few properties of one of the objects inside the array. Then you could do something like this.
const index = state.findIndex(x => x.name === field);
if(index > -1) {
const newState = [...state];
newState[index] = {
...newState[index],
isValid: isValid,
content: content,
isUnchanged: false
}
setRequiredFields(newState);
}
Find the index of the object that you want to update.
Add properties inside that object.
Update the react state.
Related
I have a JavaScript object with the following structure:
{
id: 0,
content: "English Class",
date: "Dec 24th at 7:30pm",
completed: false,
}
I also have a function const editTask(id, content) that updates the content property in the aforementioned object:
const editTask = (id, content) => {
setTasks(tasks.map((task) =>
task.id === id ? { id: task.id, content: content, date: task.date, completed: task.completed } : task));
}
Is there a way in which I can avoid passing all the object properties to only upgrade the content property as well as just spreading the rest of the object?
A common pattern is to combine the spread syntax with manually setting some properties:
const editTask = (id, content) => {
setTasks(tasks.map((task) =>
task.id === id ? { ...task, content } : task));
}
In the above example, the spread syntax sets all the properties of task, and the next line overrides the property content to the new value. It's important that the spread is before the content. If it were after, then the new value would be overridden by the spread.
I have an array object and I want to update the array object using id and props.
below is the structure of array object,
array object = [{columGrp:"All",deafultColumnName:"a",id:0},{columGrp:"ll",deafultColumnName:"ww",id:1},{columGrp:"oo",deafultColumnName:"qq",id:2},{columGrp:"qq",deafultColumnName:"ee",id:3}]
I have an editable table design and when a field changes I am passing field name, id, and changed value. based on that how can I update the object array.
const onChange=(props,value,id)=>{
//code here
}
onChange("columGrp","qwerty",1);
// result =>
array object = [{columGrp:"All",deafultColumnName:"a",id:0},{columGrp:"qwerty",deafultColumnName:"ww",id:1},{columGrp:"oo",deafultColumnName:"qq",id:2},{columGrp:"qq",deafultColumnName:"ee",id:3}]
a help would be really appreciable.
You can find the object by id from your array of objects using the filter function. If the object exists, you then change the given property, passing the given value. Objects work by reference in javascript so any changes in the found object will affect your object inside your array too.
const objectsArray = [{
columGrp: "All",
deafultColumnName: "a",
id: 0
}, {
columGrp: "ll",
deafultColumnName: "ww",
id: 1
}, {
columGrp: "oo",
deafultColumnName: "qq",
id: 2
}, {
columGrp: "qq",
deafultColumnName: "ee",
id: 3
}];
const onChange = (props, value, id) => {
const obj = objectsArray.find(x => x.id === id);
if (obj) {
obj[props] = value;
}
}
onChange("columGrp", "qwerty", 1);
console.log(objectsArray)
Given the following code:
const data = {
name: 'product',
children: [
{ name: 'title' },
{ name: 'code' },
{ name: 'images', children: [{ name: 'image1' }, { name: 'image2' }] },
],
}
let result = {}
function resolveNodes(nodes, parentNode){
if(nodes.children){
parentNode = nodes.name;
result[parentNode] = {};
nodes.children.forEach(node => resolveNodes(node, parentNode));
}else{
result[parentNode][nodes.name] = nodes.name;
}
}
resolveNodes(data);
console.log(result);
Current output
{
product: {
title:"title",
code:"code"
},
images: {
image1:"image1",
image2:"image2"
}
}
Desired output:
{
product: {
title:"title",
code:"code",
images: {
image1:"image1",
image2:"image2"
}
}
}
I'm not sure how to recursively add a nested object within the parent (product) object. I've tried many things like passing the parent and the child to the function and from there try to work out the structure of the new object however it get very messy and I know is not the ideal way to do it.
This function will need to handle at least one extra layer of children but I've just added 2 to simplify the question.
What is the most ideal way to implement this?
You could take a recursive approach and have a look to children and get a combined object, otherwise just the name as value.
Instead of giving a simplified solution, let's have a closer look to the code.
resolveNodes = ({ name, children }) => ({ [name]: children
? Object.assign(...children.map(resolveNodes))
: name
})
The first part,
resolveNodes = ({ name, children }) =>
^^^^^^^^^^^^^^^^^^
the arguments of the function, contains a destructuring assignment, where an object is expected and from this object, the named properties are taken as own variables.
The following returned expression
resolveNodes = ({ name, children }) => ({
})
contains an object with a
vvvvvvv
resolveNodes = ({ name, children }) => ({ [name]:
})
computed property name, which takes the value of the variable as property name.
The following conditional (ternary) operator ?:
children
? Object.assign(...children.map(resolveNodes))
: name
checks children.
If this value is truthy, like an array, it evaluates the part after ? and if the value is falsy, the opposite of truthy, then it takes the expression after :.
In short, if no children, then take name as property value.
The part for truthy, if children exists,
Object.assign(...children.map(resolveNodes))
returns a single object with all children properties.
Object.assign(...children.map(resolveNodes))
^^^^^^^^^^^^^ create a single object
^^^ spread syntax
^^^^^^^^^^^ return all element by using
^^^^^^^^^^^^ the actual function again
This part is easier to understand if order of execution is followed.
children.map(resolveNodes)
the inner part with a mapping of children by calling resolveNodes returns an array of objects with a single property. It looks like this
[
{ title: 'title' },
{ code: 'code' },
{ images: // this looks different, because nested
{ // children are called first
image1: 'image1',
image2: 'image2'
}
}
]
and contains objects with a single property.
To get an single object with all properties of the array, Object.assign takes objects and combines all objects to one and replaces same properties in the target object with the latest following same named property. This is not the problem here, because all properties are different. Actually the problem is to take the array as parameters for calling the function.
This problem has two solutions, one is the old call of a function with Function#apply, like
Object.assign.apply(null, children.map(resolveNodes))
The other one and used
Object.assign(...children.map(resolveNodes))
is spread syntax ..., which takes an iterable and adds the items as parameters into the function call.
const
resolveNodes = ({ name, children }) => ({ [name]: children
? Object.assign(...children.map(resolveNodes))
: name
}),
data = { name: 'product', children: [{ name: 'title' }, { name: 'code' }, { name: 'images', children: [{ name: 'image1' }, { name: 'image2' }] }] },
result = resolveNodes(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I am trying to setState of a component from an array of values.
I have these values on filtersSelected array->
["name", "date", "state"]
I want to set these values to my state like this
myState = {
...etc,
name: null,
date: null,
state: null
}
I tried using
this.setState(previousState => ({
...previousState,
...filtersSelected: null
}))
apparently it doesn't work.Can anyone help me?
In order to spread the array into an object you first need to convert the array into an object and then you can spread the object keys into the state:
this.setState((prevState) => ({
...prevState,
...filtersSelected.reduce(function(acc, item) {
return Object.assign(acc, {[item]: null})
}, {});
}))
There are a couple things to note here. First of all, when you call setState, you do not need to provide all of the previous state. setState "merges" the specific state properties that you pass into the already existing state.
It also might be helpful to make your filters selected array an object, as you are trying to use the spread , but is by no means necessary. If you want to keep the array, you can use the code below.
let filtersSelected = ["name", "date", "state"];
this.setState({name: filtersSelected[0], date: filtersSelected[1], state: filtersSelected[2]});
Or, if you make filtersSelected into an object (which I highly recommend), you can do:
let filtersSelected = {name: "name", date: "date", state: "state"};
this.setState(filtersSelected);
Convert your array to object first then use a loop to assign values to your newly created object.
let filteredSelected = ["name", "date", "state"];
let obj;
for(let i of filteredSelected){
obj[i] = null;
}
this.setState(previousState => ({
...previousState,
obj
}))
Everyone has his own way, here are some ways I like
let data = ["name", "date", "state"];
Object.values(data).map(i => this.setState({ [i]: null }))
But I don't like to iterate setState for each element
let data = Object.assign({}, ...["name", "date", "state"].map((prop) => ({[prop]: null})))
this.setState(data)
Or you can do like so
this.setState(["name", "date", "state"].reduce( ( accumulator, currentValue ) => ({...accumulator,[ currentValue ]: null}), {} ));
I have a todo list and want to set the state of that item in the array to "complete" if the user clicks on "complete".
Here is my action:
export function completeTodo(id) {
return {
type: "COMPLETE_TASK",
completed: true,
id
}
}
Here is my reducer:
case "COMPLETE_TASK": {
return {...state,
todos: [{
completed: action.completed
}]
}
}
The problem I'm having is the new state does no longer have the text associated of that todo item on the selected item and the ID is no longer there. Is this because I am overwriting the state and ignoring the previous properties? My object item onload looks like this:
Objecttodos: Array[1]
0: Object
completed: false
id: 0
text: "Initial todo"
__proto__: Object
length: 1
__proto__: Array[0]
__proto__: Object
As you can see, all I want to do is set the completed value to true.
You need to transform your todos array to have the appropriate item updated. Array.map is the simplest way to do this:
case "COMPLETE_TASK":
return {
...state,
todos: state.todos.map(todo => todo.id === action.id ?
// transform the one with a matching id
{ ...todo, completed: action.completed } :
// otherwise return original todo
todo
)
};
There are libraries to help you with this kind of deep state update. You can find a list of such libraries here: https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities
Personally, I use ImmutableJS (https://facebook.github.io/immutable-js/) which solves the issue with its updateIn and setIn methods (which are more efficient than normal objects and arrays for large objects with lots of keys and for arrays, but slower for small ones).
New state does no longer have the text associated of that todo item on
the selected item and the ID is no longer there, Is this because I am
overwriting the state and ignoring the previous properties?
Yes, because during each update you are assigning a new array with only one key completed, and that array doesn't contain any previous values. So after update array will have no previous data. That's why text and id's are not there after update.
Solutions:
1- Use array.map to find the correct element then update the value, Like this:
case "COMPLETE_TASK":
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.id ? { ...todo, completed: action.completed } : todo
)
};
2- Use array.findIndex to find the index of that particular object then update that, Like this:
case "COMPLETE_TASK":
let index = state.todos.findIndex(todo => todo.id === action.id);
let todos = [...state.todos];
todos[index] = {...todos[index], completed: action.completed};
return {...state, todos}
Check this snippet you will get a better idea about the mistake you are doing:
let state = {
a: 1,
arr: [
{text:1, id:1, completed: true},
{text:2, id:2, completed: false}
]
}
console.log('old values', JSON.stringify(state));
// updating the values
let newState = {
...state,
arr: [{completed: true}]
}
console.log('new state = ', newState);
One of the seminal design principles in React is "Don't mutate state." If you want to change data in an array, you want to create a new array with the changed value(s).
For example, I have an array of results in state. Initially I'm just setting values to 0 for each index in my constructor.
this.state = {
index:0,
question: this.props.test[0].questions[0],
results:[[0,0],[1,0],[2,0],[3,0],[4,0],[5,0]],
complete: false
};
Later on, I want to update a value in the array. But I'm not changing it in the state object. With ES6, we can use the spread operator. The array slice method returns a new array, it will not change the existing array.
updateArray = (list, index,score) => {
// updates the results array without mutating it
return [
...list.slice(0, index),
list[index][1] = score,
...list.slice(index + 1)
];
};
When I want to update an item in the array, I call updateArray and set the state in one go:
this.setState({
index:newIndex,
question:this.props.test[0].questions[newIndex],
results:this.updateArray(this.state.results, index, score)
});