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
}
}
}))
}
Related
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 } });
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.
I have an interesting question regarding React state and onChange events in the form. In my React component, I am storing information in the state, that includes objects. When a user types in a change, I would like part of that object in the state to change. As an example, here is the state I am talking about:
this.state = {
total: 0,
email: '',
creditCards: [],
selectedCreditCard: {},
billingAddresses: [],
shippingAddresses: [],
selectedBillingAddress: {},
selectedShippingAddress: {},
}
This is the state of the component, and this is my handle change function:
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
})
}
As you can probably tell, this type of function will not work for objects within the state. As an example, when a user types in their credit card number, I would like this.state.selectedCreditCard.creditCardNumber to be the thing that changes. Of course, with the way it is setup in my form:
<input
type="text"
name="selectedCreditCard.creditCardNumber"
value={selectedCreditCard.creditCardNumber}
onChange={(evt) => handleChange(evt)}
/>
The "name" being passed into the onChange is only a string, and not a pointer to a key value in one of the objects in the state. Therefore, there is no easy way for this function to work except for deconstructing each object in the state and placing every key:value directly in the state instead of within the object; doing this is a possibility, but the code becomes very cumbersome this way. I hope all of this makes sense. Is there any way to manipulate the objects within the state, and have the form pass a pointer to a key in the object, as opposed to a string? Thanks.
I understand from your description that you wish to update a nested child inside a state object attribute like below
//default state
this.state = {
total: 0,
email: '',
creditCards: [],
selectedCreditCard: {},
billingAddresses: [],
shippingAddresses: [],
selectedBillingAddress: {},
selectedShippingAddress: {},
}
to be updated to something like this
//state after handleEvent
{
total: 0,
email: '',
creditCards: [],
selectedCreditCard:{
creditCardNumber:"102131313"
},
billingAddresses: [],
shippingAddresses: [],
selectedBillingAddress: {},
selectedShippingAddress: {},
}
If thats the case.
Here's a recursion based solution to create a nested object using a "." based name string.
handleChange(event) {
//use split function to create hierarchy. example:
//selectedCreditCard.creditCardNumber -> [selectedCreditCard , creditCardNumber]
const obj = this.generate(
event.target.name.split("."),
event.target.value,
0
);
this.setState({ ...obj });
}
//use recursion to generate nested object. example
//([selectedCreditCard , creditCardNumber] , 12 , 0) -> {"selectedCreditCard":{"creditCardNumber":"12"}}
generate(arr, value, index) {
return {
[arr[index]]:
index === arr.length - 1 ? value : this.generate(arr, value, index + 1)
};
}
I have created a codesandbox demo for you to explore more here.
demo-link
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.
I have json data like this:
[{"TICKER":"APPL","ASK":192.12345,"BID":193.54321,"ISCHANGED":"NO"},
{"TICKER":"TSLA","ASK":318.98765,"BID":319.56789,"ISCHANGED":"NO"}]
On component update with new data I would like to compare/map the previous and the new one data and to identify changes using "ISCHANGED":"YES". Ticker is the Key, Bid/Ask - changing values.
Thanks
UPDATED with the example code:
this.state = {
quote: {
ticker: '',
ask: '',
bid: '',
isChanged: ''
}
handleQuoteFetched (data) {
// here Im looking for a way to compare data and quote
// and identify values changes and set isChanged Yes/No
this.setState({
quote: data
})
}
render () {
//...
<td className={quote.isChanged === 'Yes'?this.state.classCSSHighlight:''}>
{quote.rateBid}>
</td>
}
I think you want to use the setState callback function.
this.setState((prevState, newState) => {
// do comparison here
})
You can use lodash.isEqual function to do a deep comparison between 2 values.
handleQuoteFetched (data) {
// here Im looking for a way to compare data and quote
// and identify values changes and set isChanged Yes/No
this.setState(prevState => {
const nextState = _.isEqual(prevState.quote, data) ? {} : {...data, ISCHANGED: true}
return nextState
})
}