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.
Related
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()).
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.
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).
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 })
}
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();