DEV Community

Cover image for Learn Public Class Field Syntax by Refactoring a React Component
Angelika Jarosz
Angelika Jarosz

Posted on

Learn Public Class Field Syntax by Refactoring a React Component

When you are reading React code, you come across many different ways of doing things that accomplish the same thing. As a beginner this can be an easy road to confusion. One of the differences you may see is a React class component using public class field syntax.

This syntax is not React specific, but something that can be used when writing any JavaScript class. Before diving in, its useful to note that public and private class field declarations are still a feature currently in stage 3 of the TC39 process. However, this feature can be used through a build system like Babel (which you are likely already using if you are writing React code).

Personally I internalize things better when I can see some code refactored through multiple steps instead of just initial and final. We have a Fundraiser component that has some state and renders two components. One a form with two inputs for the current amount and the goal, and another that displays this data. We will be refactoring this component to use public class field syntax.

Here's the original and final component for reference.

Original Component

import React from 'react';
import ProgressForm from './ProgressForm.js';
import ProgressBar from './ProgressBar.js';

class Fundraiser extends React.Component {
  constructor(props) {
    super(props);
    this.handleAmountChange = this.handleAmountChange.bind(this);
    this.handleGoalChange= this.handleGoalChange.bind(this);
    this.state = { amount: '',
                   goal: ''
                  };
  }

  handleAmountChange(amount) {
    this.setState({ amount });
  }

  handleGoalChange(goal) {
    this.setState({ goal })
  }

  render() {
    const { goal, amount } = this.state
    return (
      <>
        <ProgressBar amount={amount} goal={goal} />
        <ProgressForm
          onAmountChange={this.handleAmountChange} amount={amount}
          onGoalChange={this.handleGoalChange} goal={goal}
        />
      </>
    );
  }
}

export default Fundraiser;

Refactored Using Public Class Field Syntax

import React from 'react';
import ProgressForm from './ProgressForm.js';
import ProgressBar from './ProgressBar.js';

class Fundraiser extends React.Component {
  state = { amount: '',
            goal: ''
          };

  handleAmountChange = (amount) => {
    this.setState({ amount });
  }

  handleGoalChange = (goal) => {
    this.setState({ goal })
  }

  render() {
    const { goal, amount } = this.state
    return (
      <>
        <ProgressBar amount={amount} goal={goal} />
        <ProgressForm
          onAmountChange={this.handleAmountChange} amount={amount}
          onGoalChange={this.handleGoalChange} goal={goal}
        />
      </>
    );
  }
}

export default Fundraiser;

Lets see what changed!

In our initial component we have a class with some methods for our event handlers, handleAmountChange and handleGoalChange, the render method, and a constructor in which we set some state, and handle the correct this binding to ensure our methods work as we expected with the correct this context.

The first thing we can easily refactor is to pull our state variable out of the constructor. Using public class field syntax we can define it directly as a property or 'field' on the class.

...
class Fundraiser extends React.Component {
  constructor(props) {
    super(props);
    this.handleAmountChange = this.handleAmountChange.bind(this);
    this.handleGoalChange= this.handleGoalChange.bind(this);
  }
  state = { amount: '',
            goal: ''
          };
...

Now the only thing left in our constructor is the this binding of our methods. We can move these out of our constructor as well by refactoring our methods into arrow functions which means we can get rid of our constructor entirely! We can do this because arrow functions don't have their own this keyword, so the this keyword is bound lexically.

...
class Fundraiser extends React.Component {
  state = { amount: '',
            goal: ''
          };

  handleAmountChange = (amount) => {
    this.setState({ amount });
  }

  handleGoalChange = (goal) => {
    this.setState({ goal })
  }
...

Our code now looks pretty readable and has less boilerplate!

But, we might not always want to use public class field syntax...

As always when writing code there are tradeoffs for the decisions that we make. When a method is added to a class, under the hood it is the same thing as adding that method to the functions prototype. When a method is on the prototype, it is only defined once regardless of how many instances of the class you create. When we use class fields to add methods as we did above, the code might look more readable, but the class fields are added to the instance itself instead of the prototype. If we create 100 instances, we would be creating 100 new copies of the method!

If you have any questions, comments, or feedback - please let me know. Follow for new weekly posts about JavaScript, React, Python, and Django!

Top comments (4)

Collapse
 
darrenvong profile image
Darren Vong • Edited

When we use class fields to add methods [...], the class fields are added to the instance itself instead of the prototype.

Interesting, I did not know this before reading your post! For maintainability, I tend to prefer clean code over worrying about micro optimisation details, but I can definitely see this adding performance overhead if lots of these components are needed for an app. I'll certainly bear this in mind when writing future React components!

Collapse
 
jung2x profile image
Jung2x

Agree, quick write & clean code first, optimisation later

Collapse
 
kakakcii profile image
kakakcii

I'd verify that bit of information before "bearing that in mind"

Collapse
 
kakakcii profile image
kakakcii

Coming from a both Java and JavaScript background, I have to say I'm quite confused by the name "public class field". As the name suggests, being a "class field" should means that you should only have "one copy", aka added to prototype. Why it turns out to be the opposite?