JavaScript Shinies: Class Properties

Class Properties allow you to use a slightly different and often simpler syntax to define the elements of your classes, and they’re a no brainer for good design.

Originally this started life as a forthcoming article on Decorators, so if you see a few mentions of decorators floating around, you have my apologies. The more I worked on it, though, the more it was obvious that Class Properties as a concept needed and earned an article of their own.

In terms of bang-for-buck, this feature is one of the best we’ve looked at, especially if you’re using React.

What problem does it solve?

Typically this is how we would set the properties on the class (or more specifically in the object) in a constructor. It’s the sort of syntax you’re likely to see a lot.

class User {
constructor(userData) {
this.name = userData.name;
this.email = userData.email;
}
}

When it creates a new object it will have a name and email parameter on it. None of this is very controversial, in JavaScript or any other language. But parts of this aren’t that typical. In other languages there’s usually a bunch of stuff in between.

class User {  // this bit is what we're talking about   constructor(name, email) {

Take the following example in C#, which would be similar to anything in PHP, Java, etc.

class User {  private string name;
private string email;
public User(string name, string email) {
this.name = name;
this.email = email;
}
}

A function with the same name as the class is a common way of doing a constructor in C-based languages. PHP prior to version 5 used to do the same thing. I’m not trying to write good C#, btw, so if you have a problem with the above keep it to yourself.

Note that private and string are not things JavaScript really has any concept of, nor am I trying to force them in here. I’m just saying that this is a relatively common place in various languages to put definitions, declarations, various bits of metadata, and default values.

Being able to set defaults in here would actually be quite useful. So what we want is to be able to do something almost kind of like this.

class User {  name = "sam";
email = "sam@example.com"
constructor(userData) {
Object.apply(this, userData);
}
}

This code has some benefits over our original. Our userData properties are all automatically set on the object, but if they’re not found in the object passed in, the object will still have some defaults. Admittedly not actually useful for something like name and email because they likely change nearly every item. But you could see that a sensible default on something like status might have value.

Actually almost kind of above is an understatement, as well as grammatically awful. This is exactly what we want. Unfortunately this syntax is not supported by default. We need to add a new feature, called Class Properties. That will allow us to use exactly this syntax and assign and define properties outside of the functions. Then the code written above will work exactly as written.

Functions as a Class Property

Simple static properties aren’t the only option here, either. We can have anything as a class property, including entire objects and especially functions.

There’s no obvious distinction between a class property that’s a function and a a class function so let’s clarify the difference in code.

class User() {
constructor(txData) {
Object.assign(this, txData);
}
// class function
setEmail(email) {
this.email = email;
}
// class property assigned to function
setName = name => {
this.name = name;
}
}

The usage of both of these is identical — myUser.setName("Matt") and myUser.setEmail("matt@example.com"). Both work exactly the same. So why use the less-standard version?

In React applications (though not exclusively) with class-based components it is reasonably common to lose the value of this in the course of doing something like a render. This means events that are bound to the component have their scope broken.

class Clicker extends Component {  constructor(props) {
super(props);
this.state = {
clickLeft: 0,
clickRight: 0
}
}
handleClickLeft() {
this.setState(clickLeft, this.state.clickLeft + 1);
}
handleClickRight = () => {
this.setState(clickRight, this.state.clickRight + 1);
}
render() {
return <div>
<div onClick={this.handleClickLeft}>Click The Left</div>
<div onClick={this.handleClickRight}>Click The Right</div>
</div>
}
}

The first one will get an error, that this is undefined. Because it is. But the second will work perfectly. The use of a fat arrow function will preserve the scope as expected.

This is typically fixed in the React world by something along the lines of onClick={this.handleClickRight.bind(this)} or onClick={() => this.handleClickRight()} which does fix it, but it’s not exactly intuitive. There are other patterns such as binding them all in the constructor, but what better solution than just not having the issue?

Another useful feature is that you can actually pull the state default definitions right out of the constructor.

class Clicker extends Component {  state = { clickLeft: 0, clickRight: 0 }  constructor(props) {
super(props);
}
...}

Often these state definitions are the only things of consequence that even exist in the constructor anyway. In this case we can see that the remaining constructor just does React boilerplate.

And in fact that’s all the default for the React Component we’re extending, so the constructor can be omitted altogether.

How To Enable This Feature

As Class Properties are a stage 3 proposal, they’re not typically all that well supported by build tools. Where it gets slightly complex is that though this feature is reasonably supported, there are more advanced features like this.

#creator = 'Matt';

This syntax creates a private property, and the dust on these is still settling.

In fact, the syntax we’re using here is actually better supported by browsers than by things like Babel. Given that’s the default development build transpiler, I’m going to assume you’re using that. Setting it up is relatively simple, depending on your build.

If you have something like webpack working there’s a fair chance it’s already using babel under the hood, probably with some default config. If you’re using a current version of Create-React-App it will support this functionality by default, so you don’t need to set anything up.

Whatever you’re using it’s possible you get it for free. You can probably most easily find out by just… trying it and seeing if everything explodes.

Assuming there’s something you need to do here, the first step is to tell babel that you need to support this feature. Start off by running the following command to install support for the babel plugin that handles this transform.

npm install --save-dev @babel/plugin-proposal-class-properties

Next step is creating an empty .babelrc file if there isn’t one already. You need your .babelrc file to contain the following.

{
"plugins": [
[
"@babel/plugin-proposal-class-properties", { "loose": true }
]
]
}

The official docs recommend a slightly simpler version that doesn’t include the object on the end, but it errored for me, and demanded I put that in.

After that, assuming all is correctly configured, it should just magically work.

I can’t recommend this syntax enough, it makes a significant difference to code readability, and often allows large chunks of boilerplate to be omitted entirely. It’s one of those rare occasions where there is a clear positive, with no downside at all.

Senior Web Developer based in Bangkok, Thailand. Javascript, Web and Blockchain Developer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store