React add unmounted component to the array - reactjs

Im trying to create multiple components for future rendering adding tham to the array like this:
widgets.push(<TextWidget fieldData={fieldData} correctionFactor={correctionFactor} />);
but in my component I'm getting
TypeError: Cannot set property 'FieldData' of undefined
class TextWidget extends Component {
FieldData = null;
CorrectionFactor = null;
state = {
FieldData: null,
CorrectionFactor: null
}
constructor(props) {
this.FieldData = props.fieldData;
this.CorrectionFactor = props.correctionFactor || 1;
}
componentDidMount() {
this.state.FieldData = this.FieldData;
this.state.CorrectionFactor = this.CorrectionFactor;
}
....
if i do smth like this.state.FieldData = props.FieldData; in a constructor then react is complaining about being unable to set state of unmounted component.

I think that you forgot call super() inside your constructor as a first line
super(props);
According to the React docs:
You should call super(props) before any other statement. Otherwise, this.props will be undefined in the constructor, which can lead to bugs.

You're committing two mistakes.
First: You should call the super(props) before manipulating the props, then use the this.props.FieldData, and you could do it even in the constructor(), when defining the state, like:
constructor(props) {
super(props);
this.state = {
FieldData: this.props.FieldData,
CorrectionFactor: this.props.CorrectionFactor || 1
};
}
Second: You shouldn't set state like you did:
this.state.FieldData = this.FieldData;
You should use the this.setState()(read the docs), like below:
this.setState({
FieldData: this.props.FieldData,
CorrectionFactor: this.props.CorrectionFactor
});

Related

Does React.createElement always call a constructor?

I have a class PexesoCard and a class Content which look like this:
const e = React.createElement;
class PexesoCard extends React.Component {
constructor(props) {
super(props);
this.value = this.props.value;
this.onClick = this.props.handleOnClick;
this.state = { state: 'unturned' };
}
componentDidMount() {
var self = this;
ReactDOM.findDOMNode(this).addEventListener("click", function() { self.onClick(self) } );
}
render() {
return e(
'div',
{ className: 'pexeso-card' },
this.state.state == 'turned' ?
e(Content, {value: this.value}) :
this.state.state == 'unturned' ?
e(Content, {value: 'unturned'}) :
e(Content, {value: 'removed'})
)
}}
class Content extends React.Component {
constructor(props) {
super(props);
this.value = this.props.value;
}
render() {
return React.createElement(
'div',
{ className: 'content' },
`${this.value}`
)
}}
Although the class Content seems useless here, it is a preparation for more complex content of pexeso cards I would like to create later.
In the function PexesoCard.props.handleOnClick the state of PexesoCard is changed to 'turned'. Then in a debugger I see that function render() is called correctly with state = 'turned' and e(Content, {value: this.value}) is called. However then the render function of Content is called where this.value = 'unturned' and this.props.value = the correct value I want to display. I don't understand how this is possible. Shouldn't new instance of the class Content be created in React.createComponent? Or what did I wrong?
React.createElement(or it's JSX syntax version of<Content propq={value1} />) does not re-create elements unconditionally. If it did, we all would have 2 bad outcomes: losing internal state(sure, not every component is destined to have it) and much lower performance.
Instead, if React sees:
Constructor is referentially the same
There is no different value for special key prop
Then React reuses existing element of the same type/constructor.
On the component's class level it means instead of constructor + componentDidMount + render there will be called componentDidUpdate + render.
All the process has name of "reconciliation" if you want to read more details.

React How to destruct deep props in constructor?

I have the following constructor in my class component:
constructor(props) {
super(props);
this.state = {
dirty: this.props.form.dirty // error happens here!
};
}
eslint returns an error for destructing the props. How is it possible for deeper props like this?
It is not a error per se. But you could use something like this to avoid the warning.
const { dirty } = props.form;
this.state = { dirty };
OR
const { form: { dirty } } = props;
this.state = { dirty };
First of all, don't use this.props inside constructor as you are getting props as an argument.
Secondly for destructuring, you could do something like this:
const {form: {dirty = 'any initial value in case of undefined'}} = props;
this.setState = {
dirty
}

React Native, construct initial state values from a object or array

Is it possible to create states in the constructor passed to that component from an array or object.
Here is what is to be achieved:
constructor(props) {
super(props);
this.state= {
Object.keys(this.props.data).map((key, i) => {
[key]: 'boo',
})
}
}
but it returns a syntax error.
The idea is that the data within this.props.data creates a series of states. The data passed to this.props.data is not known by the component in advance but is passed to it as a prop when the component is used.
You could directly declare this.state with return array of map
updated
constructor(props) {
super(props);
this.mapData = Object.keys(this.props.data).map((key, i) =>({[key]: 'boo'}))
this.state = this.pre_state ? Object.assign({}, ...this.pre_state) : {};
}

React.js Recommended Assigning `props` Directly To State

A React.js warning was thrown:
IndexPage: It is not recommended to assign props directly to state because updates to props won't be reflected in state. In most cases, it is better to use props directly.
I had the following code:
class IndexPage extends React.Component<IProps, IState> {
constructor(props) {
super(props)
this.state = props
}
}
But, when I have changed my code to:
class IndexPage extends React.Component<IProps, IState> {
constructor(props) {
super(props)
this.state = {...props}
}
}
The warning's gone.
Can you please explain why?
Because of how JS assignment of object values works, assigning one object to another means that the second variable will "point" to the same instance, or hold a reference to it:
let x = { v: 1 };
let y = x;
x.v = 2;
console.log(y.v) // prints "2"
The warning is there to prevent accidental assumptions about the props being automatically "propagated" to the state. IOW, it doesn't just work like you'd normally expect:
// assume props = { v: 1 };
this.state = props;
// now props changes to { v: 2 };
console.log(this.state.v) // still prints 1, and that's why you get the warning
The warning goes away because by destructuring, you're making it obvious that a new object is being created, and you don't expect the state to have the same value as props when they change.

Component does re-render even after I called setState?

I have the following react-native program:
class offerDetail extends Component {
constructor(props) {
super(props);
this.state = {
phone: null,
logo: null,
instagram: null
};
const { name } = this.props.doc.data();
this.ref = firebase.firestore().collection('companies').doc(name);
}
componentWillMount() {
let docObject = null;
this.ref.get().then(doc => {
let docObject = doc.data();
console.log(docObject); -----------------------------1
});
this.setState(
docObject
);
console.log(this.state); --------------------------------2
}
render() {
console.log(this.state);--------------------3
...
......
I have 3 instances where I print to the console. Only in instance number 1 does it print non null values, however, in instance 2 and 3 it prints null values. Why is instance 2 printing null if it is called right after setState?
Can it be that its not setting the state correctly and why?
setState() in React is asynchronous.
From the React docs (3rd paragraph):
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback...
If you want to access the state once it has been updated, you can add a callback like so:
this.setState({ docObject }, () => {
console.log(this.state.docObject);
});

Resources