React components went the long way from React.createClass
through ES2015 powered React.Component
to React.PureComponent
and stateless functional components. I really enjoy the idea that we don't
have to "hack" the language anymore, at least not that much as we used to. The progress in this
department is quite clear and brings not always obvious benefits. Using constructs built into the
language/transpiler instead of relying on framework's factory functions or constructors accepting
huge configuration objects future proofs your code.
With Babel or TypeScript future is already here or comes down to your risk aversion for
transpiling your code with stage-N
preset. Stage 2 is a proposal likely to be included in
the new standard. It brings class fields and static properties. React components can greatly benefit
from them, both when it comes to performance and reducing noise in your code.
Initializing state
As you should prefer stateless components over classes, sometimes you need to persist some state. To initialize state in your React component we are used to doing this in the constructor.
// BEFORE
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
}
...
}
Initializing state inside of the constructor comes with an overhead of calling super
and remembering
about props which are React's abstraction which leaks here a little. We can initialize state
directly as a class property.
// AFTER
import React, { Component } from "react";
class Counter extends Component {
state = { counter: 0 };
...
}
Due to the fact that the state
is defined on the instance, we still have access to this
during state initialization. This makes it possible to use props to initialize the state. Let's, for example, specify the number from which counter starts.
// AFTER
import React, { Component } from "react";
class Counter extends Component {
state = { counter: this.props.startsWith };
...
}
Bounded methods
It is common for event handlers to modify state. To do that handler has to be called in the component's
context which is not going to happen by default. The common source of the performance problems is passing
event handlers with a bounded state with arrow function or calling .bind(this)
on the event handler
inside the render()
call. Each of those techniques causes creation of new function inside the
render killing PureComponent
/PureRenderMixin
optimizations. The right way to approach this problem
is binding your event handler inside of the component's constructor.
// BEFORE
import React, { Component } from "react";
class Counter extends Component {
state = { counter: 0 };
constructor(props) {
super(props);
this.onIncrementClick = this.onIncrementClick.bind(this);
}
onIncrementClick() {
this.setState(this.increment);
}
increment(state) {
return { ...state, counter: state.counter + 1 };
}
render() {
return <button onClick={this.onIncrementClick}>{this.state.counter}</button>;
}
}
We can benefit from the fact that arrow function preserves context in which was defined and set handler directly as a class field.
// AFTER
import React, { Component } from "react";
class Counter extends Component {
state = { counter: 0 };
onIncrementClick = () => {
this.setState(this.increment);
}
increment(state) {
return { ...state, counter: state.counter + 1 };
}
render() {
return <button onClick={this.onIncrementClick}>{this.state.counter}</button>;
}
}
I recommend you to take it further with memoization and partial application if you are into functional programming.
Static fields
There are three common cases where static fields shine when it comes to React components. It is setting
propsTypes
, defaultProps
and childContextTypes
. We are used to setting them outside of the class.
It often makes you scroll to the very bottom of the file to learn about props which component
expects.
// BEFORE
import React, { Component, PropTypes } from "react";
class Counter extends Component {
...
}
Counter.propTypes = {
step: PropTypes.number.isRequired,
};
Defining props as static
fields allows you to keep them inside of the class and benefit from a
common convention of keeping static
s at the top of the class.
// AFTER
import React, { Component, PropTypes } from "react";
class Counter extends Component {
static propTypes = {
step: PropTypes.number.isRequired,
}
}
What is next?
I can only guess but I am sure we are going to discover great use cases for await/async, pipe operator and partial application. No matter what will come first, it is good we are ready.
Photo by Vladimir Proskurovskiy on Unsplash.