componentWillReceiveProps not update state in function - reactjs

Can someone explain to me, why in example bellow "this.state.time" in function "calcTime" is not updated after "componentWillReceiveProps"?
It is a bit strange because this.state.time in "Text" field is updated every time when component receive new props, but in function "calcTime" "this.state.time" always keep value received from "this.props.time".
Thank you.
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
export default class Time extends Component {
constructor(props){
super(props);
this.state = {
time:this.props.Time,
info:''
};
}
calcTime(){
console.log('in calcTime '+ this.state.time)
}
componentWillReceiveProps(nextProps) {
this.setState({
time:nextProps.Time
});
this.calcTime();
}
render(){
return(
<View>
<Text>{this.state.time}</Text>
</View>
);
}
}
AppRegistry.registerComponent('Time', () => Time);

setState is asynchronous, you can't expect the updated state value just after the setState. To check the updated values use callback method. Write it like this, it will print the updated value:
componentWillReceiveProps(nextProps) {
this.setState({
time : nextProps.Time
}, () => this.calcTime()
)
}
Reason As per DOC:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Check this answer: https://stackoverflow.com/a/42593250/5185595

Related

ReactJS lifecycle setState in componentDidMount

I have 2 components for demonstration of my problem:
Parent:
import React from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
import Hello from "./Hello";
class App extends React.Component {
state = {
name: "Michal"
};
componentDidMount = () => {
this.setState({ name: "Tina" });
};
componentDidUpdate(prevState) {
console.log("App componentDidUpdate", prevState, this.state);
}
handleUpdate = value => {
console.log("App handleUpdate");
this.setState({ name: value });
};
render() {
return (
<Grid>
<Row>
<Hello name={this.state.name} update={this.handleUpdate} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Child:
import * as React from "react";
class Hello extends React.PureComponent {
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;
In child component I want to set value in parent state via props function. But setState function is ignored, it works if props function is called from setTimeout.
Can you explain me why it work in setTimeout, why I should avoid this construction. And what is correct way to do it?
Hello component represent "Select", which in componentDidMount will fetch options and set default value.
Thank you.
Components initialise from the bottom up in React. So in your example Hello triggers componentDidMount, attempts to set the state in App via this.props.update, then App overrides it a split-second later when it calls its own componentDidMount. The name you set in the child component never reaches the state.
I'm not sure what the purpose of this is, hopefully only for leaning purposes as components shouldn't need to immediately set their own state when mounting. If you need to perform some logic before initialising the state in App you can use a constructor and do it there.
Regardless, the solution is remove the initial state setter in App.
It is not ignored and it does fire. You are just not observing it with your logs.
Check out:
https://codesandbox.io/s/kind-jackson-b2r2b?file=/src/App.js
In the console you will see the following execution order in the console window:
Hello componentDidMount props = Object {name: "Michal", update: function ()}
App handleUpdate value = Matus
App componentDidMount props = Object {}
Hello componentDidUpdate props = Object {name: "Tina", update: function ()}
App componentDidUpdate state = Object {}
Object {name: "Tina"}
Thus you will see the child componentDidMount fires and completes mount before the parent component completed and fires its componentDidMount, as components completes mounting from the child components up.
So you just never observe the state going to Matus because it triggers a new state change to Tina when it completes mounting.
You setState function from Hello component is ignored because of the React lifecycle. Basically App componentDidMount function overrides your state change from Hello component before it was rendered. That's why setTimeout helps, it moves your state change to the new rendering loop.
I don't know exact why you are trying to load data and pass it from the child component to parent but the good practice in React is to pass data from top to bottom. So the better solution would be to use Select component to just render the data from parent and react to user events.
<Select options={options} selected={option} handle={handleSelect} />
Reason:
React rendering is synchronous.
Rendering is a depth-first traversal
Now,
componentDidMount() {
this.props.update("Matus");
}
Is executed first, which sets the name Matus. Then the following executes -
componentDidMount = () => { this.setState({ name: "Tina" }); };
This sets the name Tina.
All of this happens on the first call-stack where the rendering happens. If we use setTimeout(), then
this.props.update("Matus");
will be moved to the second call-stack, which will be executed after the initial rendering and mounting has ended, thus setting the name Tina and triggering a re-render.
If you want to use class components, you need to use a constructor function to initialise state and pass the props from parent to child.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Michal"
};
}
// ... rest of parent component
import * as React from "react";
class Hello extends React.PureComponent {
constructor(props) {
super(props)
}
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;

React: Child component is passing it's state up to parent, but once parent has it, the parent is not updating its state

My issue here is that I have some values coming up from the SearchForm component. They're passing the correct value as arguments to the handleSearch function, but my call to setState does nothing. I've included console.logs to show what's stored in the variables. See comments in the code below.
Because my state is being passed from this component to the ResultList component as empty strings, the ResultList does not render correctly.
import React, { Component } from 'react';
import axios from 'axios';
import SearchForm from './components/search_form';
import ResultList from './components/results_list';
class App extends Component {
constructor(props) {
super(props);
this.state = { category: '', term: '', results: [] };
this.handleSearch = this.handleSearch.bind(this);
}
handleSearch(category, term) {
//This is not setting the state!!!!!
this.setState({ category, term });
//The first two console.logs successfully log the arguments to this
function
//The last two console.logs log empty strings even after the above
setState call.
console.log("Received from form: " + category);
console.log("Received from form: " + term);
console.log("#################################");
console.log(this.state.category);
console.log(this.state.term);
console.log('http://swapi.co/api/' + category + '/?search=' + term);
axios.get('http://swapi.co/api/' + category + '/?search=' +
term).then((response) => {
let results = response.data.results;
this.setState({ results });
console.log(this.state.results);
}).catch((error) => {
console.log(error);
});
}
render() {
return (
<div className="container panel panel-default">
<SearchForm handleSearch={this.handleSearch}/>
<ResultList results={this.state.results} category=
{this.state.category}/>
</div>
);
}
}
export default App;
I'll elaborate on what I said in my comment:
Component#setState defers and batches state update requests for performance. Because of this, you cannot relay on the Component.state to be updated with the new values immediately after the call.
setState provides a second argument - a callback which is called after the state update operation is performed. In your case, it would look like
this.setState({ category, term }, () => {
console.log(this.state.term, this.state.category)
// now you can use them
})
setState is not a synchronous function call, so calling setstate in the function might not update the state immediately. as from the documentation
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses. Think of
setState() as a request rather than an immediate command to update the
component. For better perceived performance, React may delay it, and
then update several components in a single pass. React does not
guarantee that the state changes are applied immediately
so console.log(this.state.category); console.log(this.state.term);
will not log the updated state. but if you put these statements in render function, you will see the correct state being set in next render.
Read more on https://facebook.github.io/react/docs/react-component.html#setstate

Render methods issue - React js

Here I'm trying to get value from DefaultOpts.jsx and update the values to setState in Filters.jsx. But I'm getting error as below :
setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
Filters.jsx
import React from 'react';
import DefaultOpts from 'DefaultOpts.jsx';
export default class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
vOptions : []
}
this.handleOptions = this.handleOptions.bind(this)
}
handleOptions(params) {
console.log(params)
this.setState({
vOptions : params
});
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
return (
<div>
<DefaultOpts handleOptions={this.handleOptions.bind(this)} />
</div>
)
}
}
DefaultOpts.jsx
import React from 'react';
class DefaultOpts extends React.Component{
constructor(props) {
super(props);
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
var optArray = "";
$.ajax({
type: "get",
url: "url-path",
success: function(data) {
optArray = data;
}
});
return (
<div>
{this.props.handleOptions(optArray)}
</div>
)
}
}
export default DefaultOpts;
I got some answers in stackoverflow but I'm not able to get what's issue in my code. Please suggest me here what's wrong in my code..
You can't call this.props.handleOptions inside the render because it will trigger setState of the parent component - and you are still inside the rendering process. That's why it complains.
Try to execute this function inside the componentDidMount (together with your ajax call)
There are several problems with your code:
1) First and main one that results in the mentioned error is the fact that by calling handleOptions in render you are calling setState that in turn starts react life cycle. This is a really bad practice and always should/can be avoided.
2) You have one more async call to $.ajax in render that does not directly result in updating state but still considered a bad practice.
To conclude - your render function must not result in any app logic being performed, its task is to render results that have already been prepared. Do all heavy/async work in componentDidMount/componentDidUpdate and you will be fine.
render will execute before didMount... so you are setting the state before it is mounted
anyway move the $.ajax call to didMount, you shouldn't be doing logic things in render()

Unable to setState to React component

I'm trying to set the state of my PlayerKey component here however the state won't update on a onClick action:
class PlayerKey extends Component {
constructor(props) {
super(props);
this.state = {
activeKeys:[]
}
}
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
})
}
render() {
return (
<div className="key" data-row-number={this.props.rowKey} data-key-number={this.props.dataKeyNumber} onClick={this.activateKey}></div>
)
}
}
I've tried console logging this.state in activateKey and it gives me the state of the component no problem (the blank array) so not sure why I can't update it?
setState method in react is asynchronous and doesn't reflect the updated state value immediately.
React may batch multiple setState() calls into a single update for performance.
So accessing the recently set state value might return the older value. To see whether the state has really been set or not, You can actually pass a function as callback in setState and see the updated state value. React Docs
As in your case, you can pass a function as callback as follows.
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
}, () => {
console.log(this.state.activeKeys); // This is guaranteed to return the updated state.
});
}
See this fiddle: JSFiddle

in react can't get new value after immediately setState

I am working with Reactjs. in which I am setting error if not properly entered.
Sample code is:
handleChange: function() {
if(shares) {
this.setState({
error_shares:''
})
}
if(this.state.error_shares === ''){
console.log('entered!!')
}
}
Here value of error_state doesn't reflect changed value in next if
Yes, that is the desired behavior.
The reason why the value has not been updated is because your component has not been re-rendered yet.
If you need such a feature, you should use the componentDidUpdate callback.
See the lifecycle methods here.
EXPLANATION:
Here is how React works:
Each component is a function of external props + internal state
If a change in either of the two is triggered, the component is queued to be re-rendered (it is asynchronous so that the client application is not blocked).
See the above link for exact sequence of operations but here is a little proof of concept.
import React from 'react';
class Example extends React.Component {
constructor(props, context) {
super(props, context);
// initial state
this.state = {
clicked: false
};
}
componentDidUpdate(prevProps, prevState) {
console.log(this.state.clicked); // this will show "true"
}
render() {
return (
<button
onClick={() => {
this.setState({clicked: true});
}}
>
</button>
);
}
}
setState is asynchronous in nature.
The second (optional) parameter is a callback function that will be
executed once setState is completed and the component is re-rendered.
so you can do something like this.
handleChange: function () {
if ( shares ) {
this.setState( {
error_shares: ''
}, function () {
if ( this.state.error_shares === '' ) {
console.log( 'entered!!' )
}
} )
}
}
source: https://facebook.github.io/react/docs/component-api.html#setstate
Yes, indeed.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
http://facebook.github.io/react/docs/component-api.html
No. You can't get update value in console just after setState. This is because as soon as setState is called, view is re-rendered. Hence to get updated value, you can check it inside render() .
render(){
if(this.state.error_shares === ''){
//your code.
}
}

Resources