In React, are there any real differences between these two implementations?
Some friends tell me that the FirstComponent is the pattern, but I don't see why. The SecondComponent seems simpler because the render is called only once.
First:
import React, { PropTypes } from 'react'
class FirstComponent extends React.Component {
state = {
description: ''
}
componentDidMount() {
const { description} = this.props;
this.setState({ description });
}
render () {
const {state: { description }} = this;
return (
<input type="text" value={description} />
);
}
}
export default FirstComponent;
Second:
import React, { PropTypes } from 'react'
class SecondComponent extends React.Component {
state = {
description: ''
}
constructor (props) => {
const { description } = props;
this.state = {description};
}
render () {
const {state: { description }} = this;
return (
<input type="text" value={description} />
);
}
}
export default SecondComponent;
Update:
I changed setState() to this.state = {} (thanks joews), However, I still don't see the difference. Is one better than other?
It should be noted that it is an anti-pattern to copy properties that never change to the state (just access .props directly in that case). If you have a state variable that will change eventually but starts with a value from .props, you don't even need a constructor call - these local variables are initialized after a call to the parent's constructor:
class FirstComponent extends React.Component {
state = {
x: this.props.initialX,
// You can even call functions and class methods:
y: this.someMethod(this.props.initialY),
};
}
This is a shorthand equivalent to the answer from #joews below. It seems to only work on more recent versions of es6 transpilers, I have had issues with it on some webpack setups. If this doesn't work for you, you can try adding the babel plugin babel-plugin-transform-class-properties, or you can use the non-shorthand version by #joews below.
You don't need to call setState in a Component's constructor - it's idiomatic to set this.state directly:
class FirstComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: props.initialX
};
}
// ...
}
See React docs - Adding Local State to a Class.
There is no advantage to the first method you describe. It will result in a second update immediately before mounting the component for the first time.
Update for React 16.3 alpha introduced static getDerivedStateFromProps(nextProps, prevState) (docs) as a replacement for componentWillReceiveProps.
getDerivedStateFromProps is invoked after a component is instantiated as well as when it receives new props. It should return an object to update state, or null to indicate that the new props do not require any state updates.
Note that if a parent component causes your component to re-render, this method will be called even if props have not changed. You may want to compare new and previous values if you only want to handle changes.
https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is static, therefore it does not have direct access to this (however it does have access to prevState, which could store things normally attached to this e.g. refs)
edited to reflect #nerfologist's correction in comments
You could use the short form like below if you want to add all props to state and retain the same names.
constructor(props) {
super(props);
this.state = {
...props
}
//...
}
YOU HAVE TO BE CAREFUL when you initialize state from props in constructor. Even if props changed to new one, the state wouldn't be changed because mount never happen again.
So getDerivedStateFromProps exists for that.
class FirstComponent extends React.Component {
state = {
description: ""
};
static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.description !== nextProps.description) {
return { description: nextProps.description };
}
return null;
}
render() {
const {state: {description}} = this;
return (
<input type="text" value={description} />
);
}
}
Or use key props as a trigger to initialize:
class SecondComponent extends React.Component {
state = {
// initialize using props
};
}
<SecondComponent key={something} ... />
In the code above, if something changed, then SecondComponent will re-mount as a new instance and state will be initialized by props.
set the state data inside constructor like this
constructor(props) {
super(props);
this.state = {
productdatail: this.props.productdetailProps
};
}
it will not going to work if u set in side componentDidMount() method through props.
If you directly init state from props, it will shows warning in React 16.5 (5th September 2018)
you could use key value to reset state when need, pass props to state it's not a good practice , because you have uncontrolled and controlled component in one place. Data should be in one place handled
read this
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
You can use componentWillReceiveProps.
constructor(props) {
super(props);
this.state = {
productdatail: ''
};
}
componentWillReceiveProps(nextProps){
this.setState({ productdatail: nextProps.productdetailProps })
}
Related
this is the first time I face this warning message.
Can't call setState on a component that is not yet mounted.
Follows:
This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the MyComponent component.
The "not yet mounted" part actually makes little to no sense as the only way to trigger the issue is to call a function by clicking a button from a component that needs to be mounted in order to see the button. The component is not unmounted at any given time neither.
This dummy component reproduces the error in my app:
import PropTypes from 'prop-types'
import React from 'react'
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state'
}
this.clickMe = this.clickMe.bind(this)
}
clickMe () {
this.setState({
some: 'new state'
})
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
)
}
}
I am using:
"react": "16.3.2",
"react-dom": "16.3.2",
"mobx": "4.2.0",
"mobx-react": "5.1.2",
Did I miss something in the latest React/mobx version? (note the component does not use any mobx related stuff but its parent is a mobx-react observer)
Edit:
There must be something related to the component instance, further investigation has shown that in some cases, creating an handler inside the render function will make this warning disappear, but not in all cases.
class MyComponent extends React.component {
constructor (props) {
// ...
this.clickMeBound = this.clickMe.bind(this)
}
clickMe () {
...
}
render () {
// works
<button onClick={() => {this.clickMe()}}>click arrow in render</button>
// warning: Can't call setState on a component that is not yet mounted.
<button onClick={this.clickMeBound}>click bound</button>
}
}
Edit 2:
I have removed 'react-hot-loader/patch' from my entries in my Webpack config and some weird issues like this one have disappeared. I'm not putting this as an answer because the error message itself is still weird and this causes a warning in the console. Everything works fine though.
This warning that you are getting is because you are setting a reference to clickMe method in the constructor, which is then using the setState().
constructor (props) {
super(props)
this.state = {
initial: 'state',
some: ''
}
this.clickMe = this.clickMe.bind(this); <--- This method
}
clickMe () {
this.setState({
some: 'new state' <-- the setState reference that is causing the issue
})
}
Try removing the this.clickMe = this.clickMe.bind(this) from constructor and do it in a lifecycle method like componentWillMount() or ComponentDidMount(). For react 16 and above you can use the componentWillMount method with "SAFE_" prefix. [SAFE_componentWillMount]
componentWillMount() {
this.clickMe = this.clickMe.bind(this);
}
clickMe () {
this.setState({
some: 'new state'
})
}
You just need to use the componentDidMount() method. As the React documentation tells in the component life cycle, if you need to load data from a remote endpoint, this is a good place to instantiate the network request and you may call setState() immediately in componentDidMount().
Like this:
componentDidMount(){
this.clickMe = this.clickMe.bind(this);
}
clickMe () {
this.setState({
some: 'new state'
})
}
Just add following line to your
code
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state',
some: '' // <------- THIS LINE
}
this.clickMe = this.clickMe.bind(this)
}
clickMe () {
this.setState({
some: 'new state'
})
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
);
}
}
You should not use setState in the constructor since the component is not mounted yet. In this case clickMe() method calls setState().
Instead, initialize the state directly. Like,
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
example if from https://reactjs.org/docs/react-component.html#constructor
The setState() is used so that we can reflect the state changes by re-rendering. Since the component is not rendered yet we can change the state directly and changes will reflect on the call of the render() method.
As #Amida mentioned, hot-loader seems to be the issue. Whoever is using
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true,
ReactHotModuleReplacement = true
});
in Startup.cs, remove it and the issue will disappear. I don't know why, but this is my current workaround.
EDIT:
Update of "react-hot-loader" and "webpack-hot-middleware" to latest versions fixed the issue
If this error is happening in one of your tests, you might need to render the component to an element before accessing it (i.e. simply doing let app = new App; is not enough). Rendering will effectively mount the component and its children, as explained in this other answer and then you will be able to use the result object to perform operations without triggering the setState error. A simple App.test.js example:
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div); // <-- App component mounted here
// without JSX: ReactDOM.render(React.createElement(App), div)
ReactDOM.unmountComponentAtNode(div);
});
test('array length decreased after removal', () => {
const div = document.createElement('div');
let app = ReactDOM.render(<App />, div); // <-- App component mounted here
const origArrLen = app.state.arr.length;
app.removeAtIndex(0);
expect(app.state.arr.length).toEqual(origArrLen - 1);
ReactDOM.unmountComponentAtNode(div);
});
Where the App component could have:
class App extends Component {
state = {
arr: [1,2,3]
};
removeAtIndex = index => {
const { arr } = this.state;
this.setState({ arr: arr.filter((el, i) => i !== index) });
};
// render() { return ( ... ) }
}
You should assign the initial state in the constructor() . then call setState() immediately in componentDidMount(). to trigger an extra rendering, before the browser updates the screen. in this case render() method will be called twice and the user won’t see the intermediate state.
Your code will look like this :
import PropTypes from 'prop-types'
import React from 'react'
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state' // initialize your state in the constructor()
}
}
componentDidMount(){
this.clickMe = this.clickMe.bind(this)
clickMe () { // call setState() in componentDidMount()
this.setState({
some: 'new state'
})
}
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
)
}
}
I am not really sure how to properly ask this question but I will explain what I'm trying to do here:
So I have this parent Component which creates a Component like so:
<CurrentTemperature cityName={this.state.cityName}></CurrentTemperature>
The CurrentTemperature Component looks like this:
import React, { Component } from "react";
import "../App.css";
export default class CurrentTemperature extends Component {
constructor(props) {
super(props);
this.state = {
temperature: 0,
cityName: this.props.cityName,
};
}
componentDidMount() {
//fetch the temperature from api here
}
render() {
return (
<div>
<div className="city-temperature">
{this.state.cityName} {this.state.temperature}
</div>
</div>
);
}
}
All I'm trying to do is read the city name from the parent, then fetch the current temperature from my API, and then display both of those in the Component. But if I try to console.log(this.props.cityName) from anywhere other than from inside the city-temperature div, I always get an empty string. What is going on here?
cityName is the state of the parent component. I guess the parent component would get the "cityName" asynchronously. right? If this is the case, You have to put the temperature in the parent component as its state. And you have to insert the API call in the parent component. CurrentTemperature component will behave like a pure function component.
const CurrentTemperature = ({temperature, cityName}) => {
return (
<div className="city-temperature">
{cityName} {temperature}
</div>
);
}
I guess this is not only the solution but also the best DX.
You can remove this in your constructor, and then use this.state.cityName
constructor(props) {
super(props);
this.state = {
temperature: 0,
cityName: props.cityName,
};
}
this is the first time I face this warning message.
Can't call setState on a component that is not yet mounted.
Follows:
This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the MyComponent component.
The "not yet mounted" part actually makes little to no sense as the only way to trigger the issue is to call a function by clicking a button from a component that needs to be mounted in order to see the button. The component is not unmounted at any given time neither.
This dummy component reproduces the error in my app:
import PropTypes from 'prop-types'
import React from 'react'
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state'
}
this.clickMe = this.clickMe.bind(this)
}
clickMe () {
this.setState({
some: 'new state'
})
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
)
}
}
I am using:
"react": "16.3.2",
"react-dom": "16.3.2",
"mobx": "4.2.0",
"mobx-react": "5.1.2",
Did I miss something in the latest React/mobx version? (note the component does not use any mobx related stuff but its parent is a mobx-react observer)
Edit:
There must be something related to the component instance, further investigation has shown that in some cases, creating an handler inside the render function will make this warning disappear, but not in all cases.
class MyComponent extends React.component {
constructor (props) {
// ...
this.clickMeBound = this.clickMe.bind(this)
}
clickMe () {
...
}
render () {
// works
<button onClick={() => {this.clickMe()}}>click arrow in render</button>
// warning: Can't call setState on a component that is not yet mounted.
<button onClick={this.clickMeBound}>click bound</button>
}
}
Edit 2:
I have removed 'react-hot-loader/patch' from my entries in my Webpack config and some weird issues like this one have disappeared. I'm not putting this as an answer because the error message itself is still weird and this causes a warning in the console. Everything works fine though.
This warning that you are getting is because you are setting a reference to clickMe method in the constructor, which is then using the setState().
constructor (props) {
super(props)
this.state = {
initial: 'state',
some: ''
}
this.clickMe = this.clickMe.bind(this); <--- This method
}
clickMe () {
this.setState({
some: 'new state' <-- the setState reference that is causing the issue
})
}
Try removing the this.clickMe = this.clickMe.bind(this) from constructor and do it in a lifecycle method like componentWillMount() or ComponentDidMount(). For react 16 and above you can use the componentWillMount method with "SAFE_" prefix. [SAFE_componentWillMount]
componentWillMount() {
this.clickMe = this.clickMe.bind(this);
}
clickMe () {
this.setState({
some: 'new state'
})
}
You just need to use the componentDidMount() method. As the React documentation tells in the component life cycle, if you need to load data from a remote endpoint, this is a good place to instantiate the network request and you may call setState() immediately in componentDidMount().
Like this:
componentDidMount(){
this.clickMe = this.clickMe.bind(this);
}
clickMe () {
this.setState({
some: 'new state'
})
}
Just add following line to your
code
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state',
some: '' // <------- THIS LINE
}
this.clickMe = this.clickMe.bind(this)
}
clickMe () {
this.setState({
some: 'new state'
})
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
);
}
}
You should not use setState in the constructor since the component is not mounted yet. In this case clickMe() method calls setState().
Instead, initialize the state directly. Like,
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
example if from https://reactjs.org/docs/react-component.html#constructor
The setState() is used so that we can reflect the state changes by re-rendering. Since the component is not rendered yet we can change the state directly and changes will reflect on the call of the render() method.
As #Amida mentioned, hot-loader seems to be the issue. Whoever is using
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true,
ReactHotModuleReplacement = true
});
in Startup.cs, remove it and the issue will disappear. I don't know why, but this is my current workaround.
EDIT:
Update of "react-hot-loader" and "webpack-hot-middleware" to latest versions fixed the issue
If this error is happening in one of your tests, you might need to render the component to an element before accessing it (i.e. simply doing let app = new App; is not enough). Rendering will effectively mount the component and its children, as explained in this other answer and then you will be able to use the result object to perform operations without triggering the setState error. A simple App.test.js example:
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div); // <-- App component mounted here
// without JSX: ReactDOM.render(React.createElement(App), div)
ReactDOM.unmountComponentAtNode(div);
});
test('array length decreased after removal', () => {
const div = document.createElement('div');
let app = ReactDOM.render(<App />, div); // <-- App component mounted here
const origArrLen = app.state.arr.length;
app.removeAtIndex(0);
expect(app.state.arr.length).toEqual(origArrLen - 1);
ReactDOM.unmountComponentAtNode(div);
});
Where the App component could have:
class App extends Component {
state = {
arr: [1,2,3]
};
removeAtIndex = index => {
const { arr } = this.state;
this.setState({ arr: arr.filter((el, i) => i !== index) });
};
// render() { return ( ... ) }
}
You should assign the initial state in the constructor() . then call setState() immediately in componentDidMount(). to trigger an extra rendering, before the browser updates the screen. in this case render() method will be called twice and the user won’t see the intermediate state.
Your code will look like this :
import PropTypes from 'prop-types'
import React from 'react'
export default class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
initial: 'state' // initialize your state in the constructor()
}
}
componentDidMount(){
this.clickMe = this.clickMe.bind(this)
clickMe () { // call setState() in componentDidMount()
this.setState({
some: 'new state'
})
}
}
render () {
return (
<div>
<button onClick={this.clickMe}>click</button>
</div>
)
}
}
I am new to react and so confused in handling and calling the onChange events.
Now , I have 2 components :-
1. Parent component -
updateField = e => {
console.log("update field e called");
this.setState({
value: e.target.value
});
};
<InputTypeahead value={this.state.value} label="Email" onChange={this.updateField} typeaheadItems={this.emailAdressess} /
where I am calling the onChange and taking the current value out. Till
now whatever I type in Input I get the value.
Now,
2.In Child component :
I want to take the value coming from this parent component and using that would like to setstate.
How to achieve this in React js ? I have tried using refs , but result was not successful.
Any Help is appreciated.Thanks.
From version i.e 16.3.0 onwards, you can make use of getDerivedStateFromProps method to update the state based on props like
class InputTypeahead extends React.Component {
state = {
value: ''
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.value !== prevState.value) {
return { value: nextProps.value};
}
return null;
}
}
According to the docs:
getDerivedStateFromProps is invoked after a component is
instantiated as well as when it receives new props. It should return
an object to update state, or null to indicate that the new props do
not require any state updates.
Before v16.3.0, you would make use of constructor along with componentWillReceiveProps like
class InputTypeahead extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value});
}
}
}
I'm brand new to React and I'm using it in combination with ES6 classes. I have a class that inherits from React.Component and renders DOM based on a single property in its state. Here's how that looks:
class LoadingScreen extends React.Component {
constructor(props) {
super(props);
this.state = { state: 'isHidden' };
showTrying() {
this.setState({ state: 'isTrying' });
}
hideAll() {
this.setState({ state: 'isHidden' });
}
render() {
switch (this.state.state) {
case 'isHidden':
return null;
case 'isTrying':
// Returns a bunch of DOM elements
}
}
In a parent class, that is not a React component (I'm attempting to migrate existing code that uses no framework), I want to:
Create an instance of LoadingScreen
Call React.render to insert it in the DOM
Call methods such as hide or showTrying on that instance to update its state
I've tried:
this.loadingScreen = new LoadingScreen();
React.render(this.loadingScreen, mountNode); // React invalid component error
// Later on...
this.loadingScreen.showTrying();
And also tried:
this.loadingScreen = React.render(React.createElement("LoadingScreen"), mountNode);
// Later on...
this.loadingScreen.showTrying(); // Undefined is not a function
Clearly I'm missing something fundamental here. :)
It would be more common that you'd set a property on the LoadingScreen component instance to control the visibility of the internal representation rather than adjusting the state via a function call on the object instance.
class LoadingScreen extends React.Component {
constructor(props) {
super(props);
}
render() {
switch (this.props.mode) {
case 'isHidden':
return null;
case 'isTrying':
// Returns a bunch of DOM elements
}
}
}
LoadingScreen.propTypes = {
mode: React.PropTypes.string
};
LoadingScreen.defaultProps = {
mode: 'isTrying'
};
Then, from the parent, you'd do something like this for example:
var currentMode = "isTrying";
React.render(<LoadingScreen mode={ currentMode } />, mountNode);
Or, another pattern is that the parent container/component uses the value of the property (I've called mode) to just not create and render the LoadingScreen component at all.
If the LoadingScreen needs to, you could copy the property value into local state as you've done.
Your second approach was close.
The first argument to React.createElement can either be a string (div, span, etc.) or a subclass of React.Component. In your case, the first argument should be LoadingScreen.
this.loadingScreen = React.render(React.createElement(LoadingScreen), mountNode);
this.loadingScreen.showTrying();