Click here to share this article on LinkedIn »

The new class “property initializers” allows you to define methods (or any property) in your React components using regular ES6 classes without worrying about binding. Yes, I’ll say that again, without binding. The idea behind it is to assign an arrow function to a property which will act like a normal method, and automatically bind this from the value of the enclosing execution context (in this case, the constructor). It’s often used to create a much “cleaner” ES6 class component.

In this post we’ll create a simple screen that renders a list of users and a text input to filter the list.

The list may contain hundreds of users so we don’t want to filter it every time user types something as it might cause performance issue when re-rendering the list. To overcome this, we can delay the filter execution for a certain amount of time, to essentially wait until a user has finished typing (debouncing the event). Quite a common situation to face, right?

In this example, we will see how a property initializer works, what are the differences compared to a regular class method, the pros-cons, and some workarounds that you can do when you face its limitations.

Before that, let’s have a quick look how the List component might look if we define it with a normal React class. For simplicity, the list is just an array of random names.

export default class List extends React.Component {

constructor() {

super(...arguments);

this.state = {

list: this.props.list,

searchInput: '',

};

this._handleInputChange = this._handleInputChange.bind(this);

this._filterList = this._filterList.bind(this);

}

componentWillUnmount() {

if (this._filterTimeout) clearTimeout(this._filterTimeout);

}

render() {

let { searchInput } = this.state;

return (

<View style={{ flex: 1 }}>

<View style={styles.searchContainer}>

<TextInput

style={styles.textInput}

placeholder="Search by name"

value={searchInput}

onChangeText={this._handleInputChange}

autoCorrect={false}

/>

<Icon name="search" size={26} color="#476DC5" />

</View>

<ScrollView>

{this.state.list.map((name, index) => {

return (

<ListItem

key={index} // please pardon this one

roundAvatar

avatar={defaultProfilePicture}

title={name}

/>

);

})}

</ScrollView>

</View>

);

}



_handleInputChange(searchInput) {

this.setState({ searchInput });

if (this._filterTimeout) {

clearTimeout(this._filterTimeout);

}

this._filterTimeout = setTimeout(this._filterList, 400);

}



_filterList() {

let filterText = this.state.searchInput.toLowerCase();

let { list } = this.props;

this.setState({

list: list.filter(name =>

name.toLowerCase().includes(filterText),

),

});

}

}

In example code above, we store the filtered list in state in order to trigger a re-render when it changes. Notice that we need to bind each method that gets passed (as a prop or callback) because passing a method as a parameter will “detach” it from it’s context ( this ). We also need to implement a constructor to define the initial state and place all our bindings, which means we need to call super and pass all the arguments to it.

Some examples you might see around the web will bind in the render() method instead of the constructor. This is slightly less efficient and requires just as much boilerplate.

This is just a small example. Imagine a larger real-world component, you may have a lot more methods inside your React class that need to be bound. With this behavior, it’s hard to maintain code consistency across a team and as it becomes less readable, collaborating with other developers becomes more difficult and error-prone.

Another small nitpick, if you’re using Flow as your code type-checker, you might realize that using super(...arguments) will decrease Flow code coverage because Flow doesn't know the shape of the arguments that you’re passing. Even worse, Flow complains about the bind line because you're overwriting your instance methods.

Flow cannot cover arguments types.

Flow complains because we overriding our instance method.

Now, let’s see how to do it with property initializers!

export default class List extends React.Component {

state = {

list: this.props.list,

searchInput: '',

};



componentWillUnmount() {

if (this._filterTimeout) clearTimeout(this._filterTimeout);

}



render() {

let { searchInput, list } = this.state;

return (

<View style={{ flex: 1 }}>

<View style={styles.searchContainer}>

<TextInput

style={styles.textInput}

placeholder="Search by name"

value={searchInput}

onChangeText={this._handleInputChange}

autoCorrect={false}

/>

<Icon name="search" size={26} color="#476DC5" />

</View>

<ScrollView>

{this.state.list.map((name, index) => {

return (

<ListItem

key={index}

roundAvatar

avatar={defaultProfilePicture}

title={name}

/>

);

})}

</ScrollView>

</View>

);

}



_handleInputChange = (searchInput) => {

this.setState({ searchInput });

if (this._filterTimeout) {

clearTimeout(this._filterTimeout);

}

this._filterTimeout = setTimeout(this._filterList, 400);

};



_filterList = () => {

let filterText = this.state.searchInput.toLowerCase();

let { list } = this.props;

this.setState({

list: list.filter(name =>

name.toLowerCase().includes(filterText),

),

});

};

}

See! We don’t need constructor or bindings anymore :)

Some benefits that we can get with this:

No need to explicitly call bind() . this is bound correctly because we use arrow function syntax.

. is bound correctly because we use arrow function syntax. No need to define a constructor method (we can define initial state outside constructor!).

You can even use the property initializers syntax for render, and all other component lifecycle methods if you like.

With all the great benefits it can bring, there are a few downsides, at least from my experience:

If you want to use your method(s) when defining state, you need to initialize the method(s) before initializing state.

Let’s change our _filterList a little. Instead of calling setState() , this method will just return a new filtered array. Then, let’s say you want to filter the list with string ‘Al’ and store it as the initial state. If we put our methods in the same order as shown above, it will throw an error this._filterList is undefined.

Calling a method while initializing state won’t work if you initialize the method after the state.

Using method at when initializing state won’t work if you initialize the method after state.

If you are facing this use case, you can either put the method before the state, like this:

export default class List extends React.Component {

// This must go first

_filterList = (filterText) => {

let { list } = this.props;

return list.filter(name =>

name.toLowerCase().includes(filterText.toLowerCase()),

);

};



state = {

list: this._filterList('Al'),

searchInput: 'Al',

};



...

}

But that makes your implementation more fragile and error prone. If another engineer on your team refactors this class, they might re-order the properties/methods for code style consistency. This could lead to hard-to find bugs that aren’t easily caught in code review (good thing you have tests, right!).



One workaround is to just go back to the constructor approach.

export default class List extends React.Component {



// This will work as expected.

constructor() {

super(...arguments);

this.state = {

list: this._filterList('Al'),

searchInput: 'Al',

};

}

...

render() {

...

}

...

_filterList = (filterText) => {

let { list } = this.props;

this.setState({

list: list.filter(name =>

name.toLowerCase().includes(filterText.toLowerCase()),

),

});

};

}

A better solution is to define filterList() as separate, pure function outside the class. This also makes it easy to test because it has no side effects.

let filterList = (list, filterText) => {

let lowerCasedFilterText = filterText.toLowerCase();

return list.filter(name =>

name.toLowerCase().includes(lowerCasedFilterText),

);

};

export default class List extends React.Component {

// This will work

state = {

list: filterList(this.props.list, 'Al'),

searchInput: 'Al',

};



...

}

Some Other Downsides:

You can’t use object destructuring when initializing state like this. You must type out this.props.x each time.

each time. There might be performance issue (you can read more in Possible Pitfalls section).

You will need Babel to transpile your code if you want to use property initializers (but you’re probably using Babel anyway if you use JSX or other modern syntax/features).

Property initializers are still an experimental feature in JS, which means it’s not yet accepted into ECMAScript official standard. For the time being, you will need Babel to transpile it using babel-plugin-transform-class-properties . But this feature is on track to become a standard and it is widely used in the JavaScript/React community, in production at many organizations and you'll see this pattern in guides around the web.

Summary