Where to set props-dependant state in a React component? - javascript

Let's say that I want to set the initial state of a component depending on a variable from the parent that is passed via props.
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
I want to set the state like this:
if (this.props.foo === 'bar') {
this.setState({foo: 'bar'});
} else {
this.setState({foo: 'notBar'});
}
I put it in ComponentDidMount() and it seems to work. However, should I move it to the constructor and use the syntax this.state = {...}? Or does it belong to ComponentWillMount()? If so, is there a guarantee that the state will be rendered in time? (foo is displayed as a text field)

Since your state is assigned based on the value of proper, a good place to handle it is to assign it in the two places
ComponentWillMount/Constructor/ComponentDidMount: These are executed only once, when the component is mounted. One more thing is that if you setState in componentWillMount or componentDidMount, the it should be atleast initialised in contructor so that you don't get an undefined state error.
ComponentWillReceiveProps: This lifeCyle function is not called at the time of mounting but everytime after that the parent rerenders, so any time the prop foo changes from parent , it can be assigned to state again here
Do it like
constructor(props) {
super(props);
this.state = {
foo: ''
}
}
componentWillMount(){
if (this.props.foo === 'bar') {
this.setState({foo: 'bar'});
} else {
this.setState({foo: 'notBar'});
}
}
componentWillReceiveProps(nextProps){
if (nextProps.foo === 'bar') {
this.setState({foo: 'bar'});
} else {
this.setState({foo: 'notBar'});
}
}

Yes, it is valid to initialize state in the constructor: React Constructor Docs
So your code would be as follows:
class MyClass extends Component {
constructor(props) {
super(props);
if (props.foo === 'bar') {
this.state = {foo: 'bar'};
} else {
this.state = {foo: 'notBar'};
}
}
}
However, be aware that any change to the props in the parent will not be updated in this component, since it's only set on the constructor.
So this is only a good way to initialize state if you do not expect the parent props to change (but that is probably rare). Have a look at Lifting State Up for a guide to architect your components in a better way.

Related

React app: props do not get updated externally

I have a React app that gets initialized as simple as:
let globalTodos = some_fetch_from_localstorage();
...
function changeGlobalTodos() {
globalTodos = another_fetch_from_localstorage();
}
...
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
Inside of the app I'm doing the following:
constructor(props) {
super(props);
this.state = {
todos: []
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.todos !== prevState.todos) {
return { todos: nextProps.todos };
} else return null;
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.todos !== this.props.todos) {
this.setState({ todos: this.props.todos });
}
}
The problem is that whenever I update globalTodos, the props on the React app don't get updated: it stays on the initial globalTodos's value.
I have tried playing with getDerivedStateFromProps is being called only on first setup of the props while componentDidUpdate never gets called :-/
What am I missing here?
I can't leave a comment, so I'll just post this here. React won't re-render unless you're updating a state.
I'd make globalTodos a state and add onto it from there using setState, then you can pass that on as a prop to the child component in your case ReactApp. You don't need to change them as states in your child component.
Example:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
globalTodos: initialFetchedArray
};
}
changeGlobalTodos() {
let newTodos = fetchNewArray;
this.setState({globalTodos: newTodos});
}
ReactDOM.render(<ReactApp todos={globalTodos} />, document.getElementById('app'));
}
//You just need your prop here, here you can do whatever you want to do with the array if it's display you can use map
class Child extends Component {
render {
return(
{this.props.todos}
)
}
}
Really the main thing here is making your globalTodos a state, using setState to change that state, and just passing that state down as a prop.

What is the correct way to access props in the constructor?

What is the correct way to access props in the constructor ? Yes i know that in the React documentation is said that
When implementing the constructor for a React.Component subclass, you
should call super(props) before any other statement. Otherwise,
this.props will be undefined in the constructor, which can lead to bugs
To be more clear , why do wee need this.props if we can just use props inside constructor
class MyComponent extends React.Component {
constructor(props) {
super(props)
console.log(props)
// -> { something: 'something', … }
// absolutely same
console.log(this.props)
// -> { something: 'something', … }
}
}
Are there some cases when to use props over this.props ?
this.props and props are interchangeable in constructor because this.props === props, as long as props is passed to super. The use of this.props allows to detect a mistake instantly:
constructor() {
super();
this.state = { foo: this.props.foo }; // this.props is undefined
}
Consistent use of this.props makes it easier to refactor constructor body:
constructor(props) {
super(props);
this.state = { foo: this.props.foo };
}
to
state = { foo: this.props.foo };
Only this. needs to be removed.
There are also typing problems with props in TypeScript, this makes this.props preferable for typed component.
This recommendation exists to prevent you from introducing errors by calling other methods on the object from the constructor, which depend on this.props. You don't want to pass props to these explicitly.
E.g. the following would be a bug, because you call doStuff before super
class MyComponent extends React.Component {
constructor(props) {
this.doStuff()
super(props)
}
doStuff() {
console.log("something is: " + this.props.something)
}
}
The correct way is - don`t use props in your constructor - just send into a parent.
But both way is working.
So, there is one special case for reading props in a constructor and it is set default state from props.
In a constructor after call super(props) are this.props and props equals. this.props = props.
Its only about what you prefer, I prefer call always this.props.
Example:
constructor(props) {
super(props)
this.state = {
isRed: this.props.color === 'red',
}
}
Be sure, that you are calling super(props) on the first line in your constructor.

Reactjs component `this.state` undefined

I'm new to reactjs and tried to make a component. I set this.state in componentWillMount() and after that I call a method:
componentWillMount() {
this.setState({ value: this.props.value || "0" });
this.changeCbState = this.changeCbState.bind(this);
this.changeCbState();
}
But in my method changeCBState this.state is undefined:
changeCbState() {
console.log(this.state.value)
}
Error: Uncaught TypeError: Cannot read property 'value' of null
The problem could be that you wouldn't have initialised state in the constructor and since this.setState is asynchronous the state may not be initialsed before you are trying to access in changeCbState function.
Also the initialisation code that you have written in componentWillMount lifecycle needs to go in constructor since the componentWillMount method is supposed to be deprecated.
constructor(props) {
super(props);
this.state = {
value: this.props.value || "0"
}
this.changeCbState = this.changeCbState.bind(this);
this.changeCbState();
}
You probably didn't define state in your constructor. use the constructor to bind methods on your class instance and to define state
class Something extends React.Component {
constructor(props) {
super(props)
this.state = { value: props.value || '0' }
this.changeCbState = this.changeCbState.bind(this);
}
componentWillMount() {
this.changeCbState();
}
changeCbState() {
console.log(this.state.value)
}
}
This problem has so many different answers, but my simple elegant favorite answer is to use arrow functions when defining such function.
class Something extends React.Component {
constructor(props) {
super(props)
this.state = { value: props.value || '0' }
}
changeCbState = () => {
console.log(this.state.value)
}
componentWillMount() {
this.changeCbState();
}
}
If you want to know more about this problem which is binding in JS. read this
setState works in an asynchronous way. That means after calling setState this.state variable is not immediately changed. So you should use callback to do something after setState.
And it is better to define this.state in constructor.
Setting the state in componentWillMount is considered an anti-pattern. Take a look at this resource https://vasanthk.gitbooks.io/react-bits/anti-patterns/04.setState-in-componentWillMount.html.
The reason why state is set to null is because you haven't defined a value previously and setState is an asynchronous operation.
You have to set the initial state as follows:
class Mycomponent extends Component {
constructor(super) {
super(props);
this.state = {
value: this.props.value || "0",
};
}
}

Reactjs: Uncaught Error: Maximum update depth exceeded when trying to update state

I am trying to do a simple thing:
check if a parameter is on the URL and then set a state for my component. This state will determine if some html code should be displayed or not.
Basically this dummy sample may give you an idea of what I have:
class Lalala extends Component {
constructor(props) {
super(props);
this.state = {showStatement : false}
}
parseURLParams(urlParams) {
if (urlParams.indexOf('blabla')> -1) {
this.setState({
showStatement: true
})
}
}
render() {
const { location } = this.prop
this.parseURLParams(location.search);
}
}
So, as you can see, every time it renders, it calls the parseURLParams function which tries to set the state, and, of course, when the setState is called, the render function is being called again, causing a infinite loop which ends up returning this error in the console.
Could you guys tell me a better way to set this state? once this is something that doesn't depend on an event, I am a bit confused.
Thanks
cause you using setState in render. It willbe render -> setState -> render-> setState -> render... You should move this.parseURLParams(location.search); to other lifecycle like this
componentWillReceiveProps(nextProps) {
if(JSON.stringify(nextProps.location) !== JSON.stringify(this.props.location)){
this.parseURLParams(this.props.location.search);
}
}
Try setting state in a different lifecycle hook, like componentDidUpdate. Also, if the prop value you want is available at the initial render, you'll want to set it in state there as well:
class Lalala extends Component {
constructor(props) {
super(props);
this.state = {
showStatement : false,
showBrokerStatements: props.location && props.location.search && props.location.search.indexOf('blabla')> -1
}
}
componentDidUpdate() {
this.parseURLParams(this.props.location.search);
}
parseURLParams(urlParams) {
if (urlParams.indexOf('blabla')> -1) {
this.setState({
showBrokerStatements: true
})
}
}
render() {
const { location } = this.prop
}
}

Access an object from componentDidMount()

I called the following library in componentDidMount() and it returns an object successfully.
componentDidMount() {
var objData =call.MyLibStart('12121', this);
}
Now I need to use objData in render() section. I also need to access some attribute of the objData such as, objData.
render() {
//I need the object.postate here
}
How can I access an object there? Is using a the state a good idea here?
You can access the object just like #3Dos's answer. If you want to modify the value of objData, then use it as a state. If you only want to render that object or get the value to check for something then a class property is enough.
Make sure you're getting the object the right way:
componentWillMount () {
this.objData = Object.assign({}, call.MyLibStart('12121', this))
console.log('objData: ', this.objData) // inspect the object in debugger to check it's keys
}
The reason with componentDidMount is it only run after the render function. Your app's flow goes like this:
in constructor(): this.objData = null
in render(): this.objData = null
in componentDidMount(): this.objData = some object
At this time render function will not be updated because it will only update if you have made some changes to your state. Since this.objData is not a state so it will always be null in render. So by changing componentDidMount to componentWillMount your objData won't be null when render got called.
What you want is probably to set an instance variable in a constructor so you can access it in your other class methods like for exemple :
class MyComponent extends Component {
constructor (props) {
super(props)
this.objData = call.MyLibStart('12121', this)
}
render () {
// Do whatever you like with this.objData
return null
}
}
Unless you need access to mounted components, you could set it as initial state in the constructor:
constructor(props) {
super(props)
this.state = {
objData: call.MyLibStart('12121', this)
}
}
render() {
// use this.state.objData here
}

Categories

Resources