Assign values from one array into another in React - javascript

I have the following structure of the state:
this.state = {
data: {
tags: []
},
items: [],
input: ''
};
When I submit the data, I am trying to assign the tags array with the items array data.
var newData = this.state.items.slice(); //copy array
this.setState({
...this.state,
data: { ...this.state.data, tags: newData }
});
The newData has all variables inside, but tags is always empty.
How can I assign exactly the same values into the tags array?
Here is my console log:
console.log(this.state.data.tags, this.state.items);
enter image description here
UPDATE:
onSubmit = (e) => {
e.preventDefault();
const errors = this.validate(this.state.data);
this.setState({ errors });
if (Object.keys(errors).length === 0) {
this.setState({ loading: true });
this.setState(prevState => ({
data: {
...prevState.data,
tags: prevState.items
}
}));
console.log(this.state.data.tags, this.state.items);
this.props
.submit(this.state.data)
.catch(err =>
this.setState({ errors: err.response.data.errors, loading: false })
);
}
};

When you set state, you don't spread the current state into the object you're passing. setState takes in an object as it's first argument and only updates the keys in that object. So, for example if we had:
this.state = {
a: 1,
b: 2
};
Using this:
this.setState({
b: 3
});
Will only update b. a will not be affected. Also, note that setState is asynchronous - and it's not guaranteed safe to reference state inside setState. Thus, React provides you with a callback to access the previous state:
this.setState(prevState => ({
data: {
...prevState.data,
tags: prevState.items
}
}));
This will only update the data object in state, and only the tag property, with the previous state's items array.

Related

the previous state is not saved - React useatate

I'm trying to save objects to an array, but I can't do it, the old state is deleted. I have two states in my component, from two different forms, the first form is just text and I get the data by "handleChange", but the second form is several objects that I want to store in an array that I get by "handleChangeArray".
const [formCompra, setFormCompra] = useState({
name: '',
lastName: '',
items: []
});
const [restForm, setRestForm] = useState();
const handleChage = (e) => {
const { name, value } = e.target;
setFormCompra({
...formCompra,
[name]: value
})
}
const handleChangeArray = (e) => {
const { name, value } = e.target;
setRestForm({
...restForm,
[name]: value
})
}
const handleSubmit = () => {
let newData = {
name: formCompra.name,
lastName: formCompra.lastName,
items: [...formCompra.items, restForm] //probably the error is here
}
console.log(newData)
}
As I mention, it is not possible to save the data in the array, I appreciate any help.
You can use the current state to set a new value, keeping all other values:
setState((current) => ({
...current,
key: newValue
}));
I think the issue may be that spread syntax only shallow copies the array, so in
const handleSubmit = () => {
let newData = {
name: formCompra.name,
lastName: formCompra.lastName,
items: [...formCompra.items, restForm] //probably the error is here
}
items is a copy of an array that points to all the original objects.
try
let newData = {
name: formCompra.name,
lastName: formCompra.lastName,
items: [...formCompra.map(x=>{...x}), {...restForm}] //probably the error is here
}

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

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

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

How to render the list in react-native-sortable-list?

How to re-render the data? I update the data but it is not output.
Sample code on the line 34.
setInterval(() => {
let d= this.state.data;
d.push({
image: 'https://placekitten.com/200/240',
text: 'Chloe3',
})
this.setState({
data: d
})
}, 1500)
I'm using the plugin: pinreact-native-sortable-list
You are not properly updating your state variable. You are mutating the state by inserting a new element into the data array. This change is not triggering any state update, hence render method is not called. As per React document, we should not mute the state directly
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Change the state update as (using ES6)
setInterval(() => {
let d= this.state.data;
const newRecord = {
image: 'https://placekitten.com/200/240',
text: 'Chloe3',
};
this.setState({
data: [ ...d, newRecord]
})
}, 1500);
Or using Array.concat
this.setState({
data: this.state.data.concat([newRecord])
});
Hope this will help!

Mutation issue while Inserting into array - Reactjs

I have a state object that looks like this.
this.state = {
formValues: {},
};
After some processing, formValues contains the following.
this.state = {
formValues: {
q1: value 1,
q2: value 2
},
};
Now i have q3 inside formValues which is an array of values. When i try to push the value like as follows
let q3 = e.target.name,
arrayValues = [1,2,3]
formValues[q3].push(arrayValues)
I am getting the following error while submitting the data
Uncaught Error: A state mutation was detected between dispatches
It looks like there is a problem with pushing data into array. Any idea on how to fix this?
You need to create a copy and update the state with setState instead of direct state mutation with push.
this.setState(prevState => ({
formValues: {
...prevState.formValues,
[q3]: prevState.formValues[q3].concat(arrayValues),
},
}));
assuming that you always want to push to your array such as in your use case:
const { formValues } = this.state
const arrayValues = [1,2,4]
const newFormValues = { ...formValues, q3: [...formValues[q3], arrayValues]}
this.setState({ formValues: newFormValues })
but much better if you control directly the value of q3:
const { formValues } = this.state
const arrayValues = [1,2,4]
const newFormValues = { ...formValues, q3: arrayValues}
this.setState({ formValues: newFormValues })

Categories