Reactjs/Javascript update object dynamically - javascript

I have an object:
const [form, setForm] = useState({});
And in useEffect it will update form:
let obj = {
id:1,
info: {information: 1, task: 2, other: 3}
}
setForm(obj)
And I want to update this object values by this function handleForm:
const handleForm = (name, value, type, object) => {
if(object){
setForm({...form, [object]: {[name]: value}})
} else {
setForm({...form, [name]: value});
}
}
Here is for example I update a key's value:
<Input value={obj.info.information} onChange={(e)=>handleForm('information', e.target.value, false, 'info')}/>
By this, I want to update info object, information key's value. It works and updates information but it going to remove other keys like task and other, actually it replaces not updating the whole object. I use ...form prevState but wondering how can I keeps prev keys on update. should I use something like ...object ?
Working Demo

In your handleForm when condition for object is hit, you replace the whole object with a single value. Instead, you want to spread existing values and add the new one to it, like so:
setForm({ ...form, [object]: { ...form[object], [name]: value } });

Related

Why is my globalState state object being updated before I update it myself?

I have a multitabbed view that I am controlling the data with through a global state, being passed through useContext (along with the setState updater function).
The structure is similar to
globalState: {
company: {
list: [
[{name: ..., ...}, {field1: ..., ... }],
[{name: ..., ...}, {field1: ..., ... }],
...
]
},
...
}
I have a table in this first tab, where each row that displays the details in the first object of each inner list array (globalState.company.list[X][0]), and has a few checkboxes to toggle fields in the second object in each inner list array (globalState.company.list[X][1]).
The issue I am having is that when I check a checkbox for a specific field, all companies have that field set to that value before I call setGlobalState(...) in that onChange call from the checkbox itself.
Here is all the related code for the flow of creating the checkbox and the handler:
<td><Checkbox
disabled={tpr.disabled} // true or false
checked={tpr.checked} // true or false
onChange={checkboxOnChange} // function handler
targetId={company.id} // number
field={"field1"} />
</td>
Checkbox definition
const Checkbox = React.memo(({ disabled, checked, onChange, targetId, field }) => {
return (
<input
type="checkbox"
style={ /* omitted */ }
defaultChecked={checked}
disabled={disabled}
onChange={(e) => onChange(e, targetId, field)}
/>
);
});
onChange Handler callback
const checkboxOnChange = (e, id, field) => {
const checked = e.target.checked;
console.log("GLOBAL STATE PRE", globalState.companies.list);
let foundCompany = globalState.companies.list.find(company => company[0].id === id);
foundCompany[1][field].checked = checked;
console.log("foundCompany", foundCompany);
console.log("GLOBAL STATE POST", globalState.companies.list);
setGlobalState(prevState => ({
...prevState,
companies: {
...prevState.companies,
list: prevState.companies.list.map(company => {
console.log("company PRE ANYTHING", company);
if (company[0].id === foundCompany[0].id) {
console.log("Inside here");
return foundCompany;
}
console.log("company", company);
return company;
})
}
}));
};
I see from the GLOBAL STATE PRE log that if I were to check a box for field1, then all companies would have field1 checked well before I modify anything else. I can confirm that before the box is checked, the globalState is as I expect it to be with all of the data and fields correctly set on load.
In the picture below, I checked the box for TPR in the second company array, and before anything else happens, the second and third companies already have the TPR set to true.
Any help would be appreciated, and I will share any more details I am able to share. Thank you.
Just don't mutate the state object directly:
const checkboxOnChange = (e, id, field) => {
const checked = e.target.checked;
setGlobalState(prevState => ({
...prevState,
companies: {
...prevState.companies,
list: prevState.companies.list.map(company => {
if (company[0].id === id) {
return {
...company,
checked
};
}
return {
...company
};
})
}
}));
};
The globalState object is being updated before you call setGlobalState because you are mutating the current state (e.g. foundCompany[1][field].checked = checked;)
One way of getting around this issue is to make a copy of the state object so that it does not refer to the current state. e.g.
var cloneDeep = require('lodash.clonedeep');
...
let clonedGlobalState = cloneDeep(globalState);
let foundCompany = clonedGlobalState.companies.list.find(company => company[0].id === id);
foundCompany[1][field].checked = checked;
I recommend using a deep clone function like Lodash's cloneDeep as using the spread operator to create a copy in your instance will create a shallow copy of the objects within your list array.
Once you have cloned the state you can safely update it to your new desired state (i.e. without worry of mutating the existing globalState object) and then refer to it when calling setGlobalState.

Mapping a variable to the state in React

I'm having a problem mapping a certain value to the state.For example, using this json var on my state (its an example only, doesnt need to make sense the json variable):
this.state={
person:
{
nose:'',
legs:{
knees:'',
foot:{
finger:'',
nail:''
}
}
}
}
What I want to to is to create this 'person' on my frontend, by user input, and the send it to my backend,but Im having a problem.
Imagine that I want to change the person.nose atribute of my state variable.
The user writes the information by using this input field:
<input name={props.name} onChange={handleChange} type="text" className="form-control" />
And this is the call that I made to that same component:
<TextInput name="nose" onChange={this.handleChange} />
When executing the handleChange method,I cant update the variable,its always empty, I have tried using on the name field on my textInput "name","this.state.person.nose" but nothing happened, its always empty.
Here is my handleChange method:
handleChange(evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
NEW EDIT:
So, now Im having this problem.This is a more realistic json object than the first one:
{
name: '',
iaobjectfactory: {
institution: '',
parameter: [{
value: '',
classtype: ''
}],
name: '',
usedefaultinstitution: '',
methodname: ''
},
ieventhandlerclass: ''
}
This is the output that my frontend should give me, but this is what the most recent solution returned me:
{
"name":"d",
"iaobjectfactory":{
"institution":"",
"parameter":[
{
"value":"",
"classtype":""
}
],
"name":"",
"usedefaultinstitution":"",
"methodname":""},
"ieventhandlerclass":"d",
"institution":"d",
"methodname":"d",
"usedefaultinstitution":"d"
}
The values that should be on the iaobjectfactory are outside of it,I dont know why, I used this:
handleChange(evt) {
const { name, value } = evt.target;
// use functional state
this.setState((prevState) => ({
// update the value in eventType object
eventType: {
// keep all the other key-value pairs
...prevState.eventType,
// update the eventType value
[name]: value
}
}))
EDIT 2:
This is the json output now.
{
"name":"",
"iaobjectfactory":{
"institution":"bb",
"parameter":[
{"value":"",
"classtype":""
}],
"name":"bb",
"usedefaultinstitution":"bb",
"methodname":"bb",
"ieventhandlerclass":"aa"},
"ieventhandlerclass":""}
Problems:The first name is not reading and the ieventhandlerclass is inside the iaobjectfactory and it shouldnt
Problem with your code is, it will create a new variable nose in state and assign the value to that key. you can access the input value using this.state.nose.
Updating nested state is not that easy, you have to take care about all the other key-value pairs. In your case nose will be accessible by this.state.person.nose, so you need to update person.nose value not nose value.
You need to write it like this:
this.handleChange(evt) {
const { name, value } = evt.target;
// use functional state
this.setState((prevState) => ({
// update the value in person object
person: {
// keep all the other key-value pairs
...prevState.person,
// update the person value
[name]: value
}
}))
}
What it will do is, it will keep all the key-value pairs as it is and only update the person.nose value.
Update:
You are trying to update value of person.iaobjectfactory.name not person.name, so you need to keep all the key-values of person.iaobjectfactory and update only one at a time, like this:
handleChange(evt) {
const { name, value } = evt.target;
// use functional state
this.setState((prevState) => ({
// update the value in eventType object
eventType: {
// keep all the other key-value pairs of eventType
...prevState.eventType,
// object which you want to update
iaobjectfactory: {
// keep all the other key-value pairs of iaobjectfactory
...prevState.eventType.iaobjectfactory,
// update
[name]: value
}
}
}))
}

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.

What's the best alternative to update nested React state property with setState()?

I've been thinking about what would be the best way among these options to update a nested property using React setState() method. I'm also opened to more efficient methods considering performance and avoiding possible conflicts with other possible concurrent state changes.
Note: I'm using a class component that extends React.Component. If you're using React.PureComponent you must be extra careful when updating nested properties because that might not trigger a re-render if you don't change any top-level property of your state. Here's a sandbox illustrating this issue:
CodeSandbox - Component vs PureComponent and nested state changes
Back to this question - My concern here is about performance and possible conflicts between other concurrent setState() calls when updating a nested property on state:
Example:
Let's say I'm building a form component and I will initialize my form state with the following object:
this.state = {
isSubmitting: false,
inputs: {
username: {
touched: false,
dirty: false,
valid: false,
invalid: false,
value: 'some_initial_value'
},
email: {
touched: false,
dirty: false,
valid: false,
invalid: false,
value: 'some_initial_value'
}
}
}
From my research, by using setState(), React will shallow merge the object that we pass to it, which means that it's only going to check the top level properties, which in this example are isSubmitting and inputs.
So we can either pass it a full newState object containing those two top-level properties (isSubmitting and inputs), or we can pass one of those properties and that will be shallow merged into the previous state.
QUESTION 1
Do you agree that it is best practice to pass only the state top-level property that we are updating? For example, if we are not updating the isSubmitting property, we should avoid passing it to setState() in other to avoid possible conflicts/overwrites with other concurrent calls to setState() that might have been queued together with this one? Is this correct?
In this example, we would pass an object with only the inputs property. That would avoid conflict/overwrite with another setState() that might be trying to update the isSubmitting property.
QUESTION 2
What is the best way, performance-wise, to copy the current state to change its nested properties?
In this case, imagine that I want to set state.inputs.username.touched = true.
Even though you could do this:
this.setState( (state) => {
state.inputs.username.touched = true;
return state;
});
You shouldn't. Because, from React Docs, we have that:
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
So, from the excerpt above we can infer that we should build a new object from the current state object, in order to change it and manipulate it as we want and pass it to setState() to update the state.
And since we are dealing with nested objects, we need a way to deep copy the object, and assuming you don't want to use any 3rd party libraries (lodash) to do so, what I've come up with was:
this.setState( (state) => {
let newState = JSON.parse(JSON.stringify(state));
newState.inputs.username.touched = true;
return ({
inputs: newState.inputs
});
});
Note that when your state has nested object you also shouldn't use let newState = Object.assign({},state). Because that would shallow copy the state nested object reference and thus you would still be mutating state directly, since newState.inputs === state.inputs === this.state.inputs would be true. All of them would point to the same object inputs.
But since JSON.parse(JSON.stringify(obj)) has its performance limitations and also there are some data types, or circular data, that might not be JSON-friendly, what other approach would you recommend to deep copy the nested object in order to update it?
The other solution I've come up with is the following:
this.setState( (state) => {
let usernameInput = {};
usernameInput['username'] = Object.assign({},state.inputs.username);
usernameInput.username.touched = true;
let newInputs = Object.assign({},state.inputs,usernameInput);
return({
inputs: newInputs
});
};
What I did in this second alternative was to create an new object from the innermost object that I'm going to update (which in this case is the username object). And I have to get those values inside the key username, and that's why I'm using usernameInput['username'] because later I will merge it into a newInputs object. Everything is done using Object.assign().
This second option has gotten better performance results. At least 50% better.
Any other ideas on this subject? Sorry for the long question but I think it illustrates the problem well.
EDIT: Solution I've adopted from answers below:
My TextInput component onChange event listener (I'm serving it through React Context):
onChange={this.context.onChange(this.props.name)}
My onChange function inside my Form Component
onChange(inputName) {
return(
(event) => {
event.preventDefault();
const newValue = event.target.value;
this.setState( (prevState) => {
return({
inputs: {
...prevState.inputs,
[inputName]: {
...prevState.inputs[inputName],
value: newValue
}
}
});
});
}
);
}
I can think of a few other ways to achieve it.
Deconstructing every nested element and only overriding the right one :
this.setState(prevState => ({
inputs: {
...prevState.inputs,
username: {
...prevState.inputs.username,
touched: true
}
}
}))
Using the deconstructing operator to copy your inputs :
this.setState(prevState => {
const inputs = {...prevState.inputs};
inputs.username.touched = true;
return { inputs }
})
EDIT
First solution using computed properties :
this.setState(prevState => ({
inputs: {
...prevState.inputs,
[field]: {
...prevState.inputs.[field],
[action]: value
}
}
}))
You can try with nested Object.Assign:
const newState = Object.assign({}, state, {
inputs: Object.assign({}, state.inputs, {
username: Object.assign({}, state.inputs.username, { touched: true }),
}),
});
};
You can also use spread operator:
{
...state,
inputs: {
...state.inputs,
username: {
...state.inputs.username,
touched: true
}
}
This is proper way to update nested property and keep state immutable.
I made a util function that updates nested states with dynamic keys.
function _recUpdateState(state, selector, newval) {
if (selector.length > 1) {
let field = selector.shift();
let subObject = {};
try {
//Select the subobject if it exists
subObject = { ..._recUpdateState(state[field], selector, newval) };
} catch {
//Create the subobject if it doesn't exist
subObject = {
..._recUpdateState(state, selector, newval)
};
}
return { ...state, [field]: subObject };
} else {
let updatedState = {};
updatedState[selector.shift()] = newval;
return { ...state, ...updatedState };
}
}
function updateState(state, selector, newval, autoAssign = true) {
let newState = _recUpdateState(state, selector, newval);
if (autoAssign) return Object.assign(state, newState);
return newState;
}
// Example
let initState = {
sub1: {
val1: "val1",
val2: "val2",
sub2: {
other: "other value",
testVal: null
}
}
}
console.log(initState)
updateState(initState, ["sub1", "sub2", "testVal"], "UPDATED_VALUE")
console.log(initState)
You pass a state along with a list of key selectors and the new value.
You can also set the autoAssign value to false to return an object that is a copy of the old state but with the new updated field - otherwise autoAssign = true with update the previous state.
Lastly, if the sequence of selectors don't appear in the object, an object and all nested objects with those keys will be created.
Use the spread operator
let {foo} = this.state;
foo = {
...foo,
bar: baz
}
this.setState({
foo
})

Best way to delete an item from Dictionary React js

I have a dictionary named CarValues in my code which contains following data:
CarValues is a dictionary initialized in the state.
dictionary: CarValues
key ==> string
Value ==> Array
key => Honda, Value => white, yellow, red, orange
key => Toyota, Value => white, yellow, green, black
Key => Volkswagen Value => 123, 456, 343
I would like to delete Honda and its value completely from CarValues. Though, I see few similar questions, I couldn't find the best solution for this question.
How can I remove an attribute from a Reactjs component's state object
This should solve your issue
yourMethod(key) {
const copyCarValues= {...this.state.CarValues}
delete copyCarValues[key]
this.setState({
CarValues: copyCarValues,
})
}
I believe in order to truly do this without mutating the state, you will need to re-create the entire state like so.
class Test extends React.Component {
state = {
thingToDelete: {},
otherStuff: {}
};
deleteThingToDelete = () => {
const {thingToDelete, ...state} = this.state;
this.setState(state);
}
}
Using the spread operator, we achieve a shallow clone, so be wary about that. The other option is to use Object.assign but that will also only offer a shallow clone but you will achieve much better browser support.
Probably arriving here a bit late, but here is a way of doing this with hooks and without actually mutating the previous state.
const sampleItems = {
'key1': { id: 1, name: 'test'},
'key2': { id: 2, name: 'test2'},
}
const Test = props => {
const [items, setItems] = useState(sampleItems);
deleteItemFromStateObject = itemKey => {
setItems(({[itemKey]: toDelete, ...rest}) => rest);
}
}
The easiest way to do this would be:
const carValues = Object.assign({}, this.state.carValues)
delete carValues[key]
this.setState({ carValues })
You can use Underscore.js or Lodash http://underscorejs.org/#omit
_.omit(copyCarValues, 'Honda');
First Initialise Array Globally
var dict = []
Add Object into Dictionary
dict.push(
{ key: "One",value: false},
{ key: "Two",value: false},
{ key: "Three",value: false});
Output :
[0: {key: "One", value: false},
1: {key: "Two", value: false},
2: {key: "Three", value: false}]
Update Object from Dictionary
Object.keys(dict).map((index) => {
if (index == 1){
dict[index].value = true
}
});
Output :
[0: {key: "One", value: false},
1: {key: "Two", value: true},
2: {key: "Three", value: false}]
Delete Object from Dictionary
Object.keys(dict).map((index) => {
if (index == 2){
dict.splice(index)
}
});
Output :
[0: {key: "One", value: false},
1: {key: "Two", value: true}]
Here is another simple enough solution to achieve this.
const myCarsValueInState = this.state.myCarsValueInState;
Object.keys(myCarsValueInState).map((index) => {
myCarsValueInState[index] = undefined; // you can update on any condition if you like, this line will update all dictionary object values.
return myCarsValueInState;
});
Simple enough.

Categories