What is happening in the this.setState({array})? - javascript

the line this.setState({array}) is it replacing the array obj in this.state...?
class myComp extends Component {
constructor(props){
super(props);
this.state = {
array: [],
mouseIsPressed: false
};
}
componentDidMount() {
const array = getInitialArray();
this.setState({array});
}
render() {
return(
);
}
}
When I console log rite after that line this.state is the same as before that line.
Also, the line is usually used like this.setState({abc: abc}); how is that line diffrent?

the line this.setState({array}) is it replacing the array obj in this.state...?
Yes.
{ array } is a shorthand for { array: array }. When you call setState and pass it an object, react will update every property listed in that object, and leave all other properties unmodified. So in this case array will be updated, but mouseIsPressed will not.
When I console log rite after that line this.state is the same as before that line.
setState is asynchronous (sometimes, at least). A log statement after setState is not guaranteed to see the new value. The purpose of setState is to cause the component to rerender, and on the new render it will have the new value. You can stick your console.log in render to verify that it is rerendering, and has new data.
It's rarely needed, but setState does allow you to pass a function to it as the second parameter. This function will be run once the setState is complete:
this.setState({ array }, () => {
console.log('all done!', this.state)
})

Related

Fill an array with an object (using setState)

I am trying to fill an array with an array of objects that was returned from a axios request. However, the array is returned empty.
export default class Todo extends Component {
constructor(props){
super(props)
this.state = { description: '', list: [] }
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.refresh();
}
refresh() {
axios.get(`${URL}?sort=-createdAt`)
.then(resp => this.setState({...this.state, description: 'Qualquer valor', list: resp.data}))
//.then(resp => console.log(resp.data))
console.log(this.state.list)
}
I initialize the array in the constructor (named "List") and then, following the refresh function, whcih is called as soon as the page loads, I receive the response of the axios request, and try to fill the "list" array with the data returned values, but it doesn't work.
Obs: I already guaranteed that the request is returning well and the "resp.data" contains the data that I want to push to the array named "list" (the response is returning an array of objects)
If you call function from constructor and in that function try to update state than react will not throw warning and will not update state.
So instead of calling function in constructor, try to call function in componentDidMount like below and try to access updated state value in callback function:-
constructor(props){
super(props)
this.state = { description: '', list: [] }
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
}
componentDidMount() {
this.refresh();
}
refresh() {
axios.get(`${URL}?sort=-createdAt`).then(resp =>
this.setState(
{
description: 'Qualquer valor',
list: resp.data
},
() => {
console.log(this.state.list); // here this will print updated list
}
)
);
}
The axios request call has to go in the componentDidMount life cycle hook, not the constructor.
Please refer to the documentation for more details: https://reactjs.org/docs/react-component.html#componentdidmount

Several troubles making simple object inspector in React

I am javascript and React newbie, so I am still a little bit confused by thinking in React concept.
I am trying to make simple object inspector in React.
Here is property row element:
class PropertyRow extends React.Component {
constructor(props) {
super(props);
this.state = {
propertyName: this.props.propertyName,
propertyValue: this.props.propertyValue
};
alert(this.props.propertyName + " evoked in constructor");
}
render() {
return (
<div>{this.props.propertyName} = {this.props.propertyValue}</div>
// <div>{this.state.propertyName} = {this.state.propertyValue}</div>
);
}
}
here in the component PropertyRows I am trying to read all properties of an object dynamically.
class PropertyRows extends React.Component {
constructor(props) {
super(props);
this.createProRows = this.createProRows.bind(this);
}
createProRows(obj) {
const propArr = [];
for (const key of Object.keys(obj)) {
const val = obj[key];
propArr.push(<PropertyRow propertyName={key} propertyValue={val} />);
}
return propArr;
}
render() {
return <div>{this.createProRows(this.props.obj)}</div>;
}
}
And here I test this marvelous code
class Express extends React.Component {
constructor(props) {
super(props);
this.state = {
soldiers: 0,
captain:'John Maverick'
};
this.doClick = this.doClick.bind(this);
}
doClick() {
const obj = {
soldiers: this.state.soldiers + 1,
country:'Australia' //add new property
};
this.setState(obj);
}
render() {
return (
<div onClick={this.doClick}>
<PropertyRows obj={this.state} />
</div>
);
}
}
ReactDOM.render(<Express />, document.getElementById("root"));
When you click on the text, you will see incrementing "soldiers" property by one. The code is buggy and I do not understand why, or perhaps I do, but I have not absolutely no idea, what how to solve it in React metalanguage.
I would expect, that dynamically created array of <PropertyRow propertyName={key} propertyValue={val}/> would be nice way to browse object properties. But it seems, that the rendered HTML DOM objects are not destroyed and recreated. They are mysteriously reattached, when the new object in the doClick function is to be expressed.
Furthermore
When create another object in doClick, the property obj.captain is still there (in the browser window), probably because the underlying HTML DOM elements are not destroyed. Adding new property country: 'Australia' seems to work OK.
When I call <PropertyRow propertyName={key} propertyValue={val}/> the second time I would expect, that constructor would be fired, because it is created and pushed in the new array. But it is not. It is fired only for the new property country: 'Australia'
It seems, that I have to somehow destroy rendered HTML DOM elements in order to force react to recreate them. But how?
Or is there another way?
I deeply apologize for this long text. I hope it's not so complicated to read.
Thanx
delete obj.captain doesn't do anything because there's no captain key in obj. captain key exists in this.state, and deleting it from it is discouraged because React state is conventionally immutable.
The use of this.state together with this.setState may potentially result in race condition, state updater function should be used instead.
It should be:
doClick() {
this.setState(state => ({
soldiers: state.soldiers + 1,
country:'Australia',
captain: undefined
}));
}
The problem with PropertyRow is that it processes props once in constructor. PropertyRow constructor is fired only once because the component has been already mounted and now it is only updated on new props (here's illustrative diagram of React lifecycle hooks).
If a state is supposed to derive from received props, getDerivedStateFromProps hook should be used, it maps a state from props before initial render and next updates. In this case a state is not needed because state properties and props don't differ. It's enough to have:
class PropertyRow extends React.Component {
render() {
return (
<div>{this.props.propertyName} = {this.props.propertyValue}</div>
);
}
}
And PropertyRow could be rewritten as functional component because it doesn't benefit from being a class.

React: Updating array inside State with spread operator returns 1,000X more results than needed

I'm trying to update an array inside of my component's state from within a .map() method being run inside the render() method. There are currently 9 objects within the array I'm mapping that I wish to extract a property from and add to the array inside the state. When console.log()ing the state to see why my page was freezing for so long I saw that it was iterating 1,000 copies of each entry.
Here's an example of one of the nine objects I'm iterating over
{
"name": "Trap_808",
"key" : "Q",
"path": "https://firebasestorage.googleapis.com/v0/b/online-coding.appspot.com/o/drum%20samples%2Ftrap-808-08.wav?alt=media&token=c3c63635-45b0-4c99-82ff-e397f1153fa0"
}
Here's how I have my state defined inside the constructor.
this.state = { currentSound: '', triggerKeys: [] };
What I'm trying to do is add the key property from the object to the triggerKeys property as the objects are iterated over. This is how I'm rendering the nine objects with the .map() method.
<ul id="pad-shell">
{
DRUM_PATCH.map( sound =>{
this.setState({ triggerKeys: [...this.state.triggerKeys, sound.key] });
console.log(this.state);
return <DrumButton
name={sound.name}
soundKey={sound.key}
sourceLink={sound.path}
trigger={this.updateSound}
/>
});
}
</ul>
I also tried updating the state like this
this.setState( prevState =>{ return { triggerKeys: [...prevState.triggerKeys, sound.key] } });
The above example is actually the one that returns 9,000 entries, the code above it returns 1,000 of the same entry. Aside from this everything else is working as expected so I don't think there's anything else going on elsewhere in my code. Can anyone spot what the problem is? If you need more code let me know.
As others have said, you should not use this.setState inside of render - doing so will most likely cause an infinite update loop.
You haven't provided enough code context to give you a definitive answer but
if DRUM_PATCH comes from props
class Component extends React.Component {
constructor (props) {
super(props)
this.state = { triggerKeys: props.drumPatch.map(s => s.key) }
}
render() {
...
}
}
if DRUM_PATCH is just a constant
this.state = { triggerKeys: props.drumPatch.map(s => s.key) }
becomes
this.state = { triggerKeys: DRUM_PATCH.map(s => s.key) }
hey i guesss you are doing it in render function , if yes then everytime it changes the state, it will rerender and change the state again , it will be an infinite loop.
this.setState({ triggerKeys: [...this.state.triggerKeys, sound.key] });
this is the culprit

React.js .push() is not a function

I'm a total rookie when it comes to React and this is most likely a simple issue, but it nearly drove me nuts.
The code is following:
import React, { Component } from 'react';
class Tile extends Component {
constructor(props) {
super(props);
this.state = {
priceLog: [],
diff: 'equal'
};
}
componentWillReceiveProps() {
let log = this.state.priceLog;
log = log.push(this.props.price);
this.setState({ priceLog: log });
console.log(this.state.priceLog);
}
render() {
return (
<div className="Tile">
Company: {this.props.name}<br/>
Price: {this.props.price}
<div className={this.state.diff}></div>
<button id={this.props.id}>Details</button>
</div>
);
}
}
export default Tile;
I get "Unhandled Rejection (TypeError): log.push is not a function" when the component is rendered. All properties passed to the component are strings.
Besides the answer from #CD, you do not want to directly manipulate a state outside the designated setState method. In this case you could use concat to return a new array and assign that to the state variable. Something like this
this.setState({ priceLog: this.state.pricelog.concat(this.props.price)});
And your second call to the console.log might not deliver the desired results since setState is an asynchronous call. If you want to access the new state variable you have to use a callback like this
this.setState({
priceLog: this.state.pricelog.concat(this.props.price)
}, () => console.log(this.state.pricelog));
push returns the new length of the array so replace:
log = log.push(this.props.price);
with:
log.push(this.props.price);

setState method causes "Warning: setState(...): Can only update a mounted or mounting component.." error

In following code cityNameLength is a number and represent the length of the name of one city.
My goal is to render multiple elements based on cityNameLength value.
So if cityNameLength is equal to 5 I'd like to have 5 span elements.
This is what I've have:
class myCitiesClass extends Component {
constructor(props) {
super(props);
this.state = {cLength: 0};
this.setState({ cLength: this.props.cityNameLength });
}
renderDivs() {
var products = []
var cNameLength = this.state.cLength
for (var p = 0; p < cNameLength; p++){
products.push( <span className='indent' key={p}>{p}</span> );
}
return products
}
render() {
return (
<View>
<Text>City name length: {this.props.cityNameLength}</Text>
<View>{this.renderDivs()}</View>
</View>
);
}
}
This is the error I get:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op.
There are two ways you can do this. If you want to render the spans before the component is mounted, get rid of the setState and just do this in the constructor:
this.state= {cLength: this.props.cityNameLength };
If you want the component to mount first - then remove the setState from the constructor and move it into componentDidMount():
componentDidMount() {
this.setState({ cLength: this.props.cityNameLength });
}
You cannot use this.setState in the constructor. And fortunately, you don't need to. State could be set to the proper value right in the assignment:
constructor(props) {
super(props);
this.state = { cLength: this.props.cityNameLength };
}
However, this code is not good too. Assigning state from props is not necessarily the mistake, but you have to know well what you're doing and why (thing is that props could be changed from the top level component at any time, and you will have to keep state in sync with overridden componentWillReceiveProps() to make your component work right). If you doubt you do, you should avoid it.
In your case it should be easy as you don't need state at all. Remove constructor, and use this.props.cityNameLength instead of this.state.cLength.
And when you modify state from componentDidMount() you are supposed to know very well what you're doing (because you're triggering another render right after your component is just rendered - why?). In 99% of cases it's the mistake. Not that fatal as doing so in componentDidUpdate, though.

Categories