ReactJS, how to copy json object to another identifying value changes - javascript

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
})
}

Related

Problems achieving required result of using the spread (...) operator with state object

I have a pimRegistration state initialization as shown in the chrome redux-devtools screen capture below. The nesting being referenced is pimRegistration (state.domain.patient):
I updated the patient.name object with the following spread operator statement:
store.update((state) => ({
...state,
...patientPath,
...{ [property]: value },
}));
...where property is the "name" property of the patient object with value. After the update, the following screenshot shows the new state:
Note that the original patient object (purple in the screenshot) is updated with the name object, duplicated and placed at the root of the state (yellow in screenshot).
I would like to overwrite the properties of the pimRegistration(state).domain.patient object, not to create a new patient object.
The state update is called as shown below.
store.update((state) => ({
...state,
...patientPath, // state.domain.patient
...{ [property]: value },
}));
I have tried my different combinations without achieving the desired result.
The complete update function is shown below.
update(property: string, path: string, value: any) {
const paths: string[] = path.split(".");
const pathReducer = (state: IRegistrationState, path_: string) => {
if (paths.length <= 0) {
return state.domain;
}
return state[path_];
};
const domainPath = state.domain;
let patientPath, nokPath, referrerPath;
if (path.includes("patient")) {
patientPath = paths.reduce(pathReducer, state);
}
if (path.includes("nok")) {
nokPath = paths.reduce(pathReducer, state);
}
if (path.includes("referrer")) {
referrerPath = paths.reduce(pathReducer, state);
}
store.update((state) => ({
...state,
...patientPath,
...{ [property]: value },
}));
}
The function above is invoked with the following statement in Angular 2.
if (this.path.includes("patient")) {
this._repo.update("name", "domain.patient", this.name);
}
Thanks
Deep updates to a store can be tricky. In your function you seem to be spreading the updates at the root rather than at the level you want the update at. This answer here outlines the usual practice to update the state. In short, something like
const newState = {
...state,
domain: {
...state.domain,
patient: {
...state.domain.patient,
[property]: value
}
}
}
Dynamically passing a path and updating this state can be… cumbersome. There are libraries that can help you do it such as immer, but you can possibly hack your way around with normal JS/TS.

Unwanted state changes in a class component with React

Long story short, I have a class component that constructs a poll. Before sending the data to the server I need to transform it a little so it fits the API request. I created a transformData method on my class component that transforms the data derived from the state. As a side effect it sets the data in separate this.state.data property so I can attach it with the API request. The problem is that the method mutates the other properties of the state.
transformData = () => {
const { title, sections } = this.state
const transformedSections = sections.map(section => {
delete section.isOpen
const transformedQuestions = section.questions.map(question => {
question.label = question.question
question.type = toUpper(question.type)
delete question.question
return question
})
section.questions = {
create: transformedQuestions,
}
return section
})
this.setState({
data: {
title,
sections: { create: transformedSections },
},
})
}
So I get this:
state: {
data: {...} //our transformed data
sections: {...} //transformed as well!!
}
instead of getting this:
state: {
data: {...} //our transformed data
sections: {...} //same before calling the method
I re-wrote the method with different approach — basically replaced all Array.map with Array.forEach and it worked as expected.
transformData = () => {
const { title, sections } = this.state
const transformedSections = []
sections.forEach(section => {
const transformedQuestions = []
section.questions.forEach(question => {
transformedQuestions.push({
label: question.question,
type: toUpper(question.type),
max: question.max,
min: question.min,
instruction: question.instruction,
isRequired: question.isRequired,
placeholder: question.placeholder,
})
})
transformedSections.push({
title: section.title,
questions: { create: transformedQuestions },
})
})
this.setState({
data: {
title,
sections: { create: transformedSections },
},
})
Can anyone explain what's going on here? How can I accidentally mutate a state property without explicitly calling this.setState on the aforementioned property? The thing is that the originally written method mutates the state even if I return the data object without calling this.setState whatsoever. Like so:
//This still mutates the state
return {
data: {
title,
sections: { create: transformedSections },
}
}
//without this!
//this.setState({
// data: {
// title,
// sections: { create: transformedSections },
// },
// })
Thanks!
javascript behave like this way,
its called variable referencing.
it works like pointer variable in C.
if your console those variable such as console.log(var1 == var2) it will show true cuz both references from same memory location
if you want to prevent mutate original variable then you have to create another brand new variable to mutate
like this way :
const { title, sections } = this.state
// create new variable following old one (spreading es6 way)
const tempSections = [...sections]
...
also
sections.forEach(section => {
const transformedQuestions = []
const tempQuestions = [...section.questions]
tempQuestions.forEach(question => {
...
always have to create a brand new variable of object/array/... to prevent auto mutation
for further info here
Issue here is of Shallow Copying :
console.log("---- before map -----" , this.state);
const { title, sections } = this.state
// sections is another object, and via map you are mutating inner objects
// beacuse of the shallow Copying
const transformedSections = sections.map(section => {
// any change on section object will direct mutate state
delete section.isOpen //<--- Here you are mutating state
return section
})
// state is muate already
console.log("---- After map -----" , this.state);
You can run the below code snippet and check both console.log, and check for "isOpen": true
Hope this will clear all your doubts :
const { useState , useEffect } = React;
class App extends React.Component {
state = {
title : "questions" ,
sections : [{
isOpen : true ,
questions : ["que1" , "que2" , "que3"]
}]
}
transfromData = () => {
console.log("---- before map -----" , this.state);
const { title, sections } = this.state
// sections is another object, and via map you are mutating inner objects
// beacuse of the shallow Copying
const transformedSections = sections.map(section => {
// any change on section object will direct mutate state
delete section.isOpen //<--- Here you are mutating state
return section
})
console.log("---- After map -----" , this.state);
}
render() {
return (
<div>
<button onClick={this.transfromData}>transfromData</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
You should never update the state without using the setState method. It is asyncronous, and if you don't set it properly you never know what might happen - and that's what you're seeing in the first part of your answer. See the docs
By doing
section.questions = {
create: transformedQuestions,
}
you are improperly altering the state, so you'll see this.state.sections transformed as well, because each element inside this.state.sections has now an attribute questions that contains create with the value transformedQuestions

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.

How to update certain object in state array based on received key from DOM

I am confused about executing spread operator and using it to update state array like that
todos: [
{
id: "1",
description: "Run",
completed: "true"
},
{
id: "2",
description: "Pick John",
completed: "false"
}]
I have objects inside my array, the examples provided after searching are using spread operator to update arrays with single object, how can I update that object with "id" that equals "key" only. My wrong function is
markTaskCompleted(e) {
var key = e._targetInst.key;
this.setState(
{
todoList: // state.todoList is todos variable
[...this.state.todoList, this.state.todoList.map(task => {
if (task.id == key) task.completed = "true";
}) ]
},
this.getTodos
);
}
The result of this array is the same array of todos (spread operator) with array of undefined items.
I have been googling for some time but couldn't really get it.
Instead of destructuring the array and using map, I typically update a single item's value with a single map that replaces the item I am updating and returns the existing value for all other items. Something like this:
this.setState((prevState) => {
return {
todoList: prevState.todoList.map((task) => {
if (task.id === key) {
return { ...task, completed: true };
} else {
return task;
}
}),
};
});
Also, notice that this example passes a function to this.setState rather than an object. If you are updating the state based on the previous state (in this example using todoList from the previous state) you should use the function method. setState is asynchronous and you could get unexpected results from using this.state to compute the new state.

Categories