Suppose we have the following setup with a parent component with two children C1 and C2:
Example: container for C1 and C2, with a state called data
-C1: input, updates state in Example through handler passed as propdisplay, shows state from Example
-C2: display, shows state from Example
Here it is in code and codepen:
class Example extends React.Component {
constructor (props) {
super(props)
this.state = { data: 'test' }
}
onUpdate (data) { this.setState({ data }) }
render () {
return (
<div>
<C1 onUpdate={this.onUpdate.bind(this)}/>
<C2 data={this.state.data}/>
</div>
)
}
}
class C1 extends React.Component {
constructor(props) {
super(props);
this.onUpdate = this.props.onUpdate;
}
render () {
return (
<div>
<input type='text' ref='myInput'/>
<input type='button' onClick={this.update.bind(this)} value='Update C2'/>
</div>
)
}
update () {
//this.props.onUpdate(this.refs.myInput.getDOMNode().value);
this.onUpdate(this.refs.myInput.getDOMNode().value);
}
}
class C2 extends React.Component {
constructor(props) {
super(props);
this.data = this.props.data;
}
render () {
return <div>{this.props.data}</div>
//return <div>{this.data}</div>
}
}
/*
* Render the above component into the div#app
*/
React.render(<Example />, document.getElementById('app'));
Notice that in C2's constructor, we have a reference to this.props.data. If we set that variable as a class attribute like this.data = this.props.data React fails to update C1 even after we click the update button and Example's this.state.data has been changed. I have commented out the line that works, which references this.props.data directly.
My first idea is that this must be illegal syntax in React. However, further testing with C1 showed that if the props passed in is a function and not state, there is no problem (see the code under C1's update function to confirm what I am talking about).
Why does this not work for state passed in as props but works for functions passed in as props? I would assume Example sees that C1 has changed data state and as a result of this, call a re-rendering of C2 which uses this.data to figure out what to render next.
Because the constructor only gets called once and not every time it gets new state or props so your class variables references the props passed initially and not the new ones because it doesn't rerun the constructor again. See constructor
The constructor for a React component is called before it is mounted
So once it's mounted, it doesn't get called again. The functions on the other hand, especially pure functions, will work fine because you didn't modify the function itself or any values within it.
If you want to update the class variables based on props change you might want to check shouldComponentUpdate or componentWillReceiveProps
So example in your C2 component, to fix it use this:
componentWillReceiveProps(nextProps) {
this.data = this.nextProps.data
}
But I think it's redundant doing that, this.props works fine most of the time.
Related
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.
I am stuck with an architecture that looks like this:
Basically, top level Component 1 defines functions and data that are passed as props to the Component 2 and then later also as props to Component 3, where the data is rendered, and the functions are invoked.
This worked fine so far, but now the issue is that Component 1 has introduced a call to an async function that returns a promise, which when resolved updates the original data later passed as props to Component 2, but Component 3 is never informed that data has been updated, and because of that it never re-renders and never shows updated data.
As I am not allowed to change this failing architecture, I need an advice on how to re-render Component 3 when Component 1 updates original props ?
Probably the error is the way that Props are passed down from First to third component, check this basic working example:
class Hello extends React.Component {
constructor(props){
super(props);
this.state = {
fromFirst: 0
}
}
componentWillMount(){
// Used just as example of async triggering
setTimeout(() => {
this.setState({fromFirst: 1})
}, 2000)
}
render() {
return (
<div>
Hello {this.props.name}
<SecondComponent fromFirst={this.state.fromFirst} />
</div>
)
}
}
class SecondComponent extends React.Component {
constructor(props){
super(props);
this.state = {
fromSecond: 2
}
}
render() {
return (
<div>
<p> From First: {this.props.fromFirst} </p>
<p> On Second: {this.state.fromSecond}</p>
<ThirdComponent fromFirst={this.props.fromFirst} fromSecond={this.state.fromSecond}/>
</div>)
}
}
class ThirdComponent extends React.Component{
constructor(props){
super(props);
this.state = {
fromThird: 2
}
}
render(){
return (
<div>
<p>Third asks of first {this.props.fromFirst}</p>
</div>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
Check working JSFiddle
In a comment you've said:
What Component 1 actually does is, it populates an object with data and dispatches that data to an action. ... at about the same time it also wait for promise to be resolved in order to update the original object that was dispatched a bit earlier...
That's the problem. You cannot modify state or props directly; docs. That means you cannot assign to properties on this.state or properties on objects it refers to. Instead, you have to replace them with new objects. E.g., you can't do this:
// WRONG
this.state.foo.bar = 42;
Instead, you have to do this:
this.setState(({foo}) => ({foo: {...foo, bar: 42}}));
We're building a simulation tool and we are trying to replace our current implementation of how our popups are handled using React.
The issue is that the state of our popup component is set to
this.state = connections[this.props.id]
that object is a global object that exists, gets created and update in a separate js file and if I go into the console and change connections[this.props.id].name from "junction 15" to "junction 12", the changes are not rendered immediately. I have to close and reopen the popup so it renders with the correct information.
This is something our architect wants, and the way he explained it was that he needs any changes made to our connections object outside of react NEED to reflected within our popup if it's open, but if the state is set to the marker and I modify the name of the marker in the object through the console, i dont understand why it's not automatically being updated in React
I've looked at trying to use the lifecycle methods, redux, mobx, js proxies, react context but I'm still learning and I think I'm making this more complicated than it should be.
Here's our simple popup with components:
let globalValue = 'initial'
class ReactButton extends React.Component {
constructor(props) {
super(props);
this.state = connections[this.props.id];
this.changeName = this.changeName.bind(this);
}
updateOutsideReactMade() {
this.setState(state);
// this.forceUpdate();
}
changeName(newName) {
connections[this.props.id].name = newName;
this.setState(connections[this.props.id]);
}
// ignore this, this was my attempt at using a lifecycle method
//componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
// if (this.props.name !== prevProps.name) {
// this.setState(this.props.name);
// }
//}
render() {
return (
<div>
<Input onChange={this.changeName} />
<Header name={this.state.name}
id={this.state.id}
/>
</div>
);
}
}
function renderReactButton(iddd, type){
ReactDOM.render(
<ReactButton id={iddd} />,
document.getElementById(`react-component-${type}-${iddd}`)
);
}
class Header extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>{this.props.name}
{this.props.id}</h1>
);
}
}
class Input extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const name = e.target.value;
this.props.onChange(name);
}
render() {
return (
<input onChange={this.handleChange}/>
);
}
}
So my question is how am i able to use an object (connections) that is global as my state for react AND if something modifies the data outside of React that it would be reflected on DOM. Right now, we have it working to where we can change the name through the react popups, but if we change the name through the console it will not update. Thank you guys!
****UPDATE**** 8/15/18
I wrapped each new object as a proxy as it was entered in my array.
connections[key] = new Proxy(polyLine, handleUpdatesMadeToMarkersOutsideOfReact);
I setup a handler:
let handleUpdatesMadeToMarkersOutsideOfReact = {
get: (connections, id) => {
return connections[id];
},
set: (connections, id, value) => {
//trigger react re-render
console.log('inside set');
//trigger react to update
return true;
}
};
Now I'm stuck trying to get the handler to trigger my react component to update. I created a class function for my component that forced the update but I was having a hard time accessing it with the way we have it setup.
Normally state is an object - giving existing object is ok. React requires setState usage to be able to process lifecycle, f.e. render with updated state. Modyfying state object from console doesn't let react to react ;)
You need some kind of observer, sth to tell react than data changed and to force render (call this.forceUpdate()).
The render method of this component does use any of the props supplied to the component.
Will the component re-render when the props change regardless?
class MyComponent extends React.Component {
constructor(props) {
super(props);
const { propValue } = props;
// do something with propValue...
}
render () {
return (
<div>foo</div>
);
}
}
Will render be called - yes. Unless you implement shouldComponentUpdate to return false.
Will the DOM be rerendered - no.
Also you might want to take a look at https://babeljs.io/docs/plugins/transform-react-constant-elements/ that hoists static elements up.
In
const Hr = () => {
return <hr className="hr" />;
};
Out
const _ref = <hr className="hr" />;
const Hr = () => {
return _ref;
};
Yes, the component will re-render unless you implement shouldComponentUpdate. You can inherit from PureComponent which uses shallow comparison of prop and state with previous values to determine if component should update or not.
As far as i know react will call the render method in the following scenarios
when your component get mounted initially
when state got changed using this.setState()
when your component receives new props
when this.forceUpdate() get called.
since you didn't implement shouldcomponentUpdate() the render method is going to get called
I'm using React + Electron + Redux in my app development. I was able to update the parent state from a child component in another case, but now I'm not able to do it, the state is only being updated to the child components.
I know that the reducer action is being called with the right value, but the parent component is being rerendered with the wrong one (the previous one), only the sub tree of the child component is being rendered with right value.
My method:
I'm creating a function (action handler) in the parent component container:
class CreateExerciseCanvas extends React.Component {
focusOnSection (section) { /* this is the function that i'm refering to */
store.dispatch(actions.focusOnSection(section))
}
render() {
return (
<CreateExerciseCanvas
focusOnSection={ this.focusOnSection }
/>
)
}
}
const mapStateToProps = function (store) {
return {
focusOnSection: store.exercise.focusOnSection
}
}
export default connect(mapStateToProps)(CreateExerciseCanvasContainer)
And this function is being passed as a prop to the child container:
<Index focusOnSection={ this.props.focusOnSection }/>
Lastly, the method is being used as an onClick handler in the child view.
Isn't this the right way of updating a parent with redux + react?
You have to bind your this context to the focusOnSection function in your constructor, or else it doesn't know what this is.
Try adding a constructor like so to your CreateExerciseCanvas:
constructor(props) {
super(props);
this.focusOnSection = this.focusOnSection.bind(this);
}
This is probably this most annoying part about using ES6 classes.
If you check the value of this.props inside focusOnSection (section), you will see that it is undefined. This is because focusOnSection () {} is the short syntax of focusOnSection: function () {}, which is binding this to the function, so there is no more this.props.
One solution would be hard-binding this to the class in the constructor:
constructor(props) {
super(props);
this.focusOnSection = this.focusOnSection.bind(this);
}
The other would be using an arrow function like focusOnSelection = () => {} which doesn't bind this. This latter solution only works if you are using babel (check the es2015 preset).