The pros/cons of React class property initializers - javascript

With React, I know you can initialize component state like this:
class Foo extends Component {
constructor() {
super();
this.state = { count: 0 };
}
}
And if state needs to be initialized with props:
class Foo extends Component {
constructor(props) {
super(props);
this.state = { this.props.count: 0 };
}
}
However, using the transform-class-properties plugin, you can initialize state like so:
class Foo extends Component {
state = { count: 0 };
}
Since this refers to the instance under class during construction, initial state can still use props: state = { this.props.count: 0 }
Besides the obvious benefit of less lines, I wanted to know what are some pros/cons of this syntax.
*Examples don't include binding of class methods as I know binding can be done when declaring those methods with fat arrow syntax.

Pros:
less code, more implicit
Con:
you can't do any step-by-step calculations like with constructor
e.g
constructor(props){
super(props)
const z = props.x - props.y;
const g = props.a + props.b;
const total = z ** g;
const shouldBeOpened = total > 1000;
this.state = {
shouldBeOpened,
initialSomething: z > g,
}
}

Related

React Component changing from function to class - State and state hook

I've got a question, because i can't find answer anywhere.
The problem is: what is the initial value of the following hook:
"Function" Example:
function Example() {
const [settings, setSettings] = useState({});
return (
...
);
}
And here i'm writing this function "Example" as class, but i don't know how to initialize state
(is it empty string or empty list or 0 value?)
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
settings: <-- and what here?
};
}
useState is passed the initial value as its only param - in your case {}.
You can accomplish the same in your second code snippet like so:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
settings: {}
};
}
}
Further reading:
The React docs have an example almost identical to yours.
useState source

Seamless way to bind reference to a class method with class instance [duplicate]

I'm new to using ES6 classes with React, previously I've been binding my methods to the current object (show in first example), but does ES6 allow me to permanently bind a class function to a class instance with arrows? (Useful when passing as a callback function.) I get errors when I try to use them as you can with CoffeeScript:
class SomeClass extends React.Component {
// Instead of this
constructor(){
this.handleInputChange = this.handleInputChange.bind(this)
}
// Can I somehow do this? Am i just getting the syntax wrong?
handleInputChange (val) => {
console.log('selectionMade: ', val);
}
So that if I were to pass SomeClass.handleInputChange to, for instance setTimeout, it would be scoped to the class instance, and not the window object.
Your syntax is slightly off, just missing an equals sign after the property name.
class SomeClass extends React.Component {
handleInputChange = (val) => {
console.log('selectionMade: ', val);
}
}
This is an experimental feature. You will need to enable experimental features in Babel to get this to compile. Here is a demo with experimental enabled.
To use experimental features in babel you can install the relevant plugin from here. For this specific feature, you need the transform-class-properties plugin:
{
"plugins": [
"transform-class-properties"
]
}
You can read more about the proposal for Class Fields and Static Properties here
No, if you want to create bound, instance-specific methods you will have to do that in the constructor. However, you can use arrow functions for that, instead of using .bind on a prototype method:
class SomeClass extends React.Component {
constructor() {
super();
this.handleInputChange = (val) => {
console.log('selectionMade: ', val, this);
};
…
}
}
There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.
Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.
class SomeClass extends React.Component {
constructor() {
super();
this.handleInputChange = this.handleInputChange.bind(this);
…
}
handleInputChange(val) {
console.log('selectionMade: ', val, this);
}
}
You are using arrow function and also binding it in constructor. So you no need to do binding when you use arrow functions
class SomeClass extends React.Component {
handleInputChange = (val) => {
console.log('selectionMade: ', val);
}
}
OR you need to bind a function only in constructor when you use normal function like below
class SomeClass extends React.Component {
constructor(props){
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(val){
console.log('selectionMade: ', val);
}
}
Also binding a function directly in render is not recommended. It should always be in constructor
I know this question has been sufficiently answered, but I just have a small contribution to make (for those who don't want to use the experimental feature). Because of the problem of having to bind multiple function binds in the constructor and making it look messy, I came up with a utility method that once bound and called in the constructor, does all the necessary method bindings for you automatically.
Assume I have this class with the constructor:
//src/components/PetEditor.jsx
import React from 'react';
class PetEditor extends React.Component {
constructor(props){
super(props);
this.state = props.currentPet || {tags:[], photoUrls: []};
this.tagInput = null;
this.htmlNode = null;
this.removeTag = this.removeTag.bind(this);
this.handleChange = this.handleChange.bind(this);
this.modifyState = this.modifyState.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.addTag = this.addTag.bind(this);
this.removeTag = this.removeTag.bind(this);
this.savePet = this.savePet.bind(this);
this.addPhotoInput = this.addPhotoInput.bind(this);
this.handleSelect = this.handleSelect.bind(this);
}
// ... actual method declarations omitted
}
It looks messy, doesn't it?
Now I created this utility method
//src/utils/index.js
/**
* NB: to use this method, you need to bind it to the object instance calling it
*/
export function bindMethodsToSelf(objClass, otherMethodsToIgnore=[]){
const self = this;
Object.getOwnPropertyNames(objClass.prototype)
.forEach(method => {
//skip constructor, render and any overrides of lifecycle methods
if(method.startsWith('component')
|| method==='constructor'
|| method==='render') return;
//any other methods you don't want bound to self
if(otherMethodsToIgnore.indexOf(method)>-1) return;
//bind all other methods to class instance
self[method] = self[method].bind(self);
});
}
All I now need to do is import that utility, and add a call to my constructor, and I don't need to bind each new method in the constructor anymore.
New constructor now looks clean, like this:
//src/components/PetEditor.jsx
import React from 'react';
import { bindMethodsToSelf } from '../utils';
class PetEditor extends React.Component {
constructor(props){
super(props);
this.state = props.currentPet || {tags:[], photoUrls: []};
this.tagInput = null;
this.htmlNode = null;
bindMethodsToSelf.bind(this)(PetEditor);
}
// ...
}

Clearing state es6 React

I am trying to clear a components state but can't find a reference for the es6 syntax. I was using:
this.replaceState(this.getInitialState());
however this does not work with the es6 class syntax.
How would I achieve the same result?
To the best of my knowledge, React components don't keep a copy of the initial state, so you'll just have to do it yourself.
const initialState = {
/* etc */
};
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = initialState;
}
reset() {
this.setState(initialState);
}
/* etc */
}
Beware that the line this.state = initialState; requires you never to mutate your state, otherwise you'll pollute initialState and make a reset impossible. If you can't avoid mutations, then you'll need to create a copy of initialState in the constructor. (Or make initialState a function, as per getInitialState().)
Finally, I'd recommend you use setState() and not replaceState().
Problem
The accepted answer:
const initialState = {
/* etc */
};
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = initialState;
}
reset() {
this.setState(initialState);
}
/* etc */
}
unfortunately is not correct.
initialState is passed as a reference to this.state, so whenever you change state you also change initialState (const doesn't really matter here). The result is that you can never go back to initialState.
Solution
You have to deep copy initialState to state, then it will work. Either write a deep copy function yourself or use some existing module like this.
This is the solution implemented as a function:
Class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}
getInitialState = () => ({
/* state props */
})
resetState = () => {
this.setState(this.getInitialState());
}
}
The solutions that involve setting this.state directly aren't working for me in React 16, so here is what I did to reset each key:
const initialState = { example: 'example' }
...
constructor() {
super()
this.state = initialState
}
...
reset() {
const keys = Object.keys(this.state)
const stateReset = keys.reduce((acc, v) => ({ ...acc, [v]: undefined }), {})
this.setState({ ...stateReset, ...initialState })
}
First, you'll need to store your initial state when using the componentWillMount() function from the component lifecycle:
componentWillMount() {
this.initialState = this.state
}
This stores your initial state and can be used to reset the state whenever you need by calling
this.setState(this.initialState)
const initialState = {
a: '',
b: '',
c: ''
};
class ExampleComponent extends Component {
state = { ...initialState } // use spread operator to avoid mutation
handleReset = this.handleReset.bind(this);
handleReset() {
this.setState(initialState);
}
}
Remember that in order to be able to reset the state it is important not to mutate initialState.
state = {...initialState} // GOOD
// => state points to a new obj in memory which has the values of initialState
state = initialState // BAD
// => they point to the same obj in memory
The most convenient way would be to use ES6 Spread Operator. But you could also use Object.assign instead. They would both achieve the same.
state = Object.assign({}, initialState); // GOOD
state = {...initialState}; // GOOD
In most cases you dont need a deep copy, rarely initial state is object of objects, so using spread operator which babel transpiles to the object.assign should be fine.
So, inside constructor you would have:
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value,
key2: value
}
this.initialState = { ...this.state }
}
}
From there you can use
this.setState(this.initialState);
to reset. But if for some reason your initial state is more complex object, use some library.
This is how I handle it:
class MyComponent extends React.Component{
constructor(props){
super(props);
this._initState = {
a: 1,
b: 2
}
this.state = this._initState;
}
_resetState(){
this.setState(this._initState);
}
}
Update:
Actually this is wrong. For future readers please refer to #RaptoX answer.
Also, you can use an Immutable library in order to prevent weird state modification caused by reference assignation.
I will add to the above answer that the reset function should also assign state like so:
reset() {
this.state = initialState;
this.setState(initialState);
}
The reason being that if your state picks up a property that wasn't in the initial state, that key/value won't be cleared out, as setState just updates existing keys. Assignment is not enough to get the component to re-render, so include the setState call as well -- you could even get away with this.setState({}) after the assignment.
In some circumstances, it's sufficient to just set all values of state to null.
If you're state is updated in such a way, that you don't know what might be in there, you might want to use
this.setState(Object.assign(...Object.keys(this.state).map(k => ({[k]: null}))))
Which will change the state as follows
{foo: 1, bar: 2, spam: "whatever"} > {foo: null, bar: null, spam: null}
Not a solution in all cases, but works well for me.
You can clone your state object, loop through each one, and set it to undefined. This method is not as good as the accepted answer, though.
const clonedState = this.state;
const keys = Object.keys(clonedState);
keys.forEach(key => (clonedState[key] = undefined));
this.setState(clonedState);
class MyComponent extends Component {
constructor(props){
super(props)
this.state = {
inputVal: props.inputValue
}
// preserve the initial state in a new object
this.baseState = this.state
}
resetForm = () => {
this.setState(this.baseState)
}
}
use deep copy, you can do it with lodash:
import _ from "lodash";
const INITIAL_STATE = {};
constructor(props) {
super(props);
this.state = _.cloneDeep(INITIAL_STATE);
}
reset() {
this.setState(_.cloneDeep(INITIAL_STATE));
}
class x extends Components {
constructor() {
super();
this.state = {
name: 'mark',
age: 32,
isAdmin: true,
hits: 0,
// since this.state is an object
// simply add a method..
resetSelectively() {
//i don't want to reset both name and age
// THIS IS FOR TRANSPARENCY. You don't need to code for name and age
// it will assume the values in default..
// this.name = this.name; //which means the current state.
// this.age = this.age;
// do reset isAdmin and hits(suppose this.state.hits is 100 now)
isAdmin = false;
hits = 0;
}// resetSelectively..
}//constructor..
/* now from any function i can just call */
myfunction() {
/**
* this function code..
*/
resetValues();
}// myfunction..
resetValues() {
this.state.resetSelectively();
}//resetValues
/////
//finally you can reset the values in constructor selectively at any point
...rest of the class..
}//class

React + ES6: defaultProps of hierarchy

Due I refactor my code to ES6, I move all defaults to SomeClass.defaultProps = { ... }.
Suppose a situation, when there is a class hierarchy, and I need to keep some defaults to whole hierarchy. But the problem is that defaultProps not work for classes that are extended:
class AbstractComponent extends React.Component {
constructor(props) { super(props) }
}
class OneOfImplementations extends AbstractComponent {
constructor(props) { super(props) }
}
//Problem: hierarchy defaults doesn't work
AbstractComponent.defaultProps = { name: 'Super' }
Fiddle example
P.S. I'm wondering where is the best place to keep commons (variables/functions) for the whole hierarchy? Maybe do something like this at AbstractComponent:
constructor(props) {
super(_.assign(props, {
commonValue: 128,
commonCallback: _.noop
}));
}
But the problem is that's become impossible to override one of properties from a subclass
Alternatively if you're using the stage: 0 stage: 2 preset in Babel (or the transform directly) you can use es7's proposed static property:
class AbstractComponent extends React.PureComponent {
static defaultProps = { name: 'Super' }
// Bonus: you can also set instance properties like this
state = {
someState: true,
}
// ^ Combined with new arrow binding syntax, you often don't need
// to override the constructor (for state or .bind(this) reasons)
onKeyPress = () => {
// ...
}
}
It seems like the order of declaration of the "defaultProps" property is important:
class AbstractComponent extends React.Component {
constructor(props) { super(props) }
render() {
return <div>Prop: [ {this.props.name} ]</div>
}
}
AbstractComponent.defaultProps = { name: 'Super' }
class ComponentImpl1 extends AbstractComponent {
constructor(props) { super(props) }
}
// works
http://jsfiddle.net/jwm6k66c/103/

How to use arrow functions (public class fields) as class methods?

I'm new to using ES6 classes with React, previously I've been binding my methods to the current object (show in first example), but does ES6 allow me to permanently bind a class function to a class instance with arrows? (Useful when passing as a callback function.) I get errors when I try to use them as you can with CoffeeScript:
class SomeClass extends React.Component {
// Instead of this
constructor(){
this.handleInputChange = this.handleInputChange.bind(this)
}
// Can I somehow do this? Am i just getting the syntax wrong?
handleInputChange (val) => {
console.log('selectionMade: ', val);
}
So that if I were to pass SomeClass.handleInputChange to, for instance setTimeout, it would be scoped to the class instance, and not the window object.
Your syntax is slightly off, just missing an equals sign after the property name.
class SomeClass extends React.Component {
handleInputChange = (val) => {
console.log('selectionMade: ', val);
}
}
This is an experimental feature. You will need to enable experimental features in Babel to get this to compile. Here is a demo with experimental enabled.
To use experimental features in babel you can install the relevant plugin from here. For this specific feature, you need the transform-class-properties plugin:
{
"plugins": [
"transform-class-properties"
]
}
You can read more about the proposal for Class Fields and Static Properties here
No, if you want to create bound, instance-specific methods you will have to do that in the constructor. However, you can use arrow functions for that, instead of using .bind on a prototype method:
class SomeClass extends React.Component {
constructor() {
super();
this.handleInputChange = (val) => {
console.log('selectionMade: ', val, this);
};
…
}
}
There is an proposal which might allow you to omit the constructor() and directly put the assignment in the class scope with the same functionality, but I wouldn't recommend to use that as it's highly experimental.
Alternatively, you can always use .bind, which allows you to declare the method on the prototype and then bind it to the instance in the constructor. This approach has greater flexibility as it allows modifying the method from the outside of your class.
class SomeClass extends React.Component {
constructor() {
super();
this.handleInputChange = this.handleInputChange.bind(this);
…
}
handleInputChange(val) {
console.log('selectionMade: ', val, this);
}
}
You are using arrow function and also binding it in constructor. So you no need to do binding when you use arrow functions
class SomeClass extends React.Component {
handleInputChange = (val) => {
console.log('selectionMade: ', val);
}
}
OR you need to bind a function only in constructor when you use normal function like below
class SomeClass extends React.Component {
constructor(props){
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(val){
console.log('selectionMade: ', val);
}
}
Also binding a function directly in render is not recommended. It should always be in constructor
I know this question has been sufficiently answered, but I just have a small contribution to make (for those who don't want to use the experimental feature). Because of the problem of having to bind multiple function binds in the constructor and making it look messy, I came up with a utility method that once bound and called in the constructor, does all the necessary method bindings for you automatically.
Assume I have this class with the constructor:
//src/components/PetEditor.jsx
import React from 'react';
class PetEditor extends React.Component {
constructor(props){
super(props);
this.state = props.currentPet || {tags:[], photoUrls: []};
this.tagInput = null;
this.htmlNode = null;
this.removeTag = this.removeTag.bind(this);
this.handleChange = this.handleChange.bind(this);
this.modifyState = this.modifyState.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.addTag = this.addTag.bind(this);
this.removeTag = this.removeTag.bind(this);
this.savePet = this.savePet.bind(this);
this.addPhotoInput = this.addPhotoInput.bind(this);
this.handleSelect = this.handleSelect.bind(this);
}
// ... actual method declarations omitted
}
It looks messy, doesn't it?
Now I created this utility method
//src/utils/index.js
/**
* NB: to use this method, you need to bind it to the object instance calling it
*/
export function bindMethodsToSelf(objClass, otherMethodsToIgnore=[]){
const self = this;
Object.getOwnPropertyNames(objClass.prototype)
.forEach(method => {
//skip constructor, render and any overrides of lifecycle methods
if(method.startsWith('component')
|| method==='constructor'
|| method==='render') return;
//any other methods you don't want bound to self
if(otherMethodsToIgnore.indexOf(method)>-1) return;
//bind all other methods to class instance
self[method] = self[method].bind(self);
});
}
All I now need to do is import that utility, and add a call to my constructor, and I don't need to bind each new method in the constructor anymore.
New constructor now looks clean, like this:
//src/components/PetEditor.jsx
import React from 'react';
import { bindMethodsToSelf } from '../utils';
class PetEditor extends React.Component {
constructor(props){
super(props);
this.state = props.currentPet || {tags:[], photoUrls: []};
this.tagInput = null;
this.htmlNode = null;
bindMethodsToSelf.bind(this)(PetEditor);
}
// ...
}

Categories

Resources