Many features have been added to JavaScript in ES6. These changes help developers write code that is short, easy to understand and maintain. You already support these changes when you create the React app using create-react-app. This is because it uses babel.js to convert ES6 + code into ES5 code that all browsers can understand.

In this article, we’ll explore various ways to write shorter, simpler, and easier to understand React code.

Look at the following code:

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      number1: "".number2: "".result: "".errorMsg: ""
    };
    this.onFirstInputChange = this.onFirstInputChange.bind(this);
    this.onSecondInputChange = this.onSecondInputChange.bind(this);
    this.handleAdd = this.handleAdd.bind(this);
    this.handleSubtract = this.handleSubtract.bind(this);
  }

  onFirstInputChange(event) {
    const value = event.target.value;
    this.setState({
      number1: value
    });
  }

  onSecondInputChange(event) {
    const value = event.target.value;
    this.setState({
      number2: value
    });
  }

  handleAdd(event) {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const sum = number1 + number2;
    if (isNaN(sum)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: sum }); }}handleSubtract(event) {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const subtraction = number1 - number2;
    if (isNaN(subtraction)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: subtraction }); }}render() {
    return (
      <div>
        <div className="input-section">
          {this.state.errorMsg && (
            <p className="error-msg">{this.state.errorMsg}</p>
          )}
          <label>First Number</label>
          <input
            type="text"
            name="number1"
            placeholder="Enter a number"
            value={this.state.number1}
            onChange={this.onFirstInputChange}
          />
        </div>
        <div className="input-section">
          <label>Second Number</label>
          <input
            type="text"
            name="number2"
            placeholder="Enter a number"
            value={this.state.number2}
            onChange={this.onSecondInputChange}
          />
        </div>
        <div className="result-section">
          Result: <span className="result">{this.state.result}</span>
        </div>
        <button type="button" className="btn" onClick={this.handleAdd}>
          Add
        </button>
        <button type="button" className="btn" onClick={this.handleSubtract}>
          Subtract
        </button>
      </div>); }}Copy the code

Here, we have two input text boxes to receive input from the user, and two buttons to calculate the addition and subtraction of the numbers provided as input.

1. Avoid manually binding event handlers

As you know in React, when we attach any onClick or onChange or any other event handler, it looks like this:

<input
  ...
  onChange={this.onFirstInputChange}
/>
Copy the code

Then, the handler (onFirstInputChange) does not preserve this binding.

This is not a React problem, but rather the way JavaScript event handlers work.

Therefore, we must use the.bind method to properly bind it like this:

constructor(props) {
  this.onFirstInputChange = this.onFirstInputChange.bind(this);
  this.onSecondInputChange = this.onSecondInputChange.bind(this);
  this.handleAdd = this.handleAdd.bind(this);
  this.handleSubtract = this.handleSubtract.bind(this);
}
Copy the code

The above line of code correctly maintains the binding of this class in the handler function.

However, adding new binding code for each new event handler is tedious. Fortunately, we can fix this using class attribute syntax.

Using class attributes allows us to define attributes directly inside a class.

For Babel > = 7, create-react-app uses @babel/babel-plugin-transform-class-properties internally. For Babel <7, create-react-app uses @babel/babel-plugin-transform-class-properties internally. The Babel/plugin-proposal-class-properties plug-in is used, so you don’t have to configure it manually.

To use it, we need to convert the event handler to the arrow function syntax.


onFirstInputChange(event) {
  const value = event.target.value;
  this.setState({
    number1: value
  });
}
Copy the code

The above code can be written as follows :(arrow function rewrite)

onFirstInputChange = (event) = > {
  const value = event.target.value;
  this.setState({
    number1: value
  });
}
Copy the code

In a similar way, we can convert the other three functions:

onSecondInputChange = (event) = > {
 // your code
}

handleAdd = (event) = > {
 // your code
}

handleSubtract = (event) = > {
 // your code
}
Copy the code

Likewise, there is no need to bind event handlers in the constructor. Therefore, we can remove the code. Now, the constructor will look like this:

constructor(props) {
  super(props);

  this.state = {
    number1: "".number2: "".result: "".errorMsg: ""
  };
}
Copy the code

We can simplify it even further. The class attribute syntax allows us to declare any variable directly inside the class, so we can remove the constructor entirely and declare the state as part of the class, as follows:

export default class App extends React.Component {
  state = {
    number1: "".number2: "".result: "".errorMsg: ""
  };

  render(){}}Copy the code

After the overall rewriting:

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    number1: "".number2: "".result: "".errorMsg: ""
  };

  onFirstInputChange = (event) = > {
    const value = event.target.value;
    this.setState({
      number1: value
    });
  };

  onSecondInputChange = (event) = > {
    const value = event.target.value;
    this.setState({
      number2: value
    });
  };

  handleAdd = (event) = > {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const sum = number1 + number2;
    if (isNaN(sum)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: sum }); }}; handleSubtract =(event) = > {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const subtraction = number1 - number2;
    if (isNaN(subtraction)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: subtraction }); }};render() {
    return (
      <div>
        <div className="input-section">
          {this.state.errorMsg && (
            <p className="error-msg">{this.state.errorMsg}</p>
          )}
          <label>First Number</label>
          <input
            type="text"
            name="number1"
            placeholder="Enter a number"
            value={this.state.number1}
            onChange={this.onFirstInputChange}
          />
        </div>
        <div className="input-section">
          <label>Second Number</label>
          <input
            type="text"
            name="number2"
            placeholder="Enter a number"
            value={this.state.number2}
            onChange={this.onSecondInputChange}
          />
        </div>
        <div className="result-section">
          Result: <span className="result">{this.state.result}</span>
        </div>
        <button type="button" className="btn" onClick={this.handleAdd}>
          Add
        </button>
        <button type="button" className="btn" onClick={this.handleSubtract}>
          Subtract
        </button>
      </div>); }}Copy the code

2. Use a single event handler method

If you examine the code above, you will see that we have a separate event handler function onFirstInputChange and onSecondInputChange for each input field.

If the number of input fields increases, then the number of event handler functions also increases, which is not good.

For example, if you were to create a registration page, there would be many input fields. Therefore, it is not feasible to create a separate handler function for each input field.

Let’s change it.

To create a single event handler that will handle all input fields, we need to give each input field a unique name that exactly matches the corresponding state variable name.

We already have this setup. We also define in the state the names number1 and number2 that we specify for the input fields. Therefore, we change the handler methods for the two input fields to onInputChange, as follows:

<input
  type="text"
  name="number1"
  placeholder="Enter a number"
  onChange={this.onInputChange}
/>

<input
  type="text"
  name="number2"
  placeholder="Enter a number"
  onChange={this.onInputChange}
/>
Copy the code

And add a new onInputChange event handler as follows:

onInputChange = (event) = > {
  const name = event.target.name;
  const value = event.target.value;
  this.setState({
    [name]: value
  });
};
Copy the code

Here, when setting the state, we use the dynamic value to set the dynamic state name. Therefore, when we change the value of the number1 input field, event.target.name will be number1 and event.target.value will be the value entered by the user.

When we change the value of the number2 input field, event.target.name will be number2 and event.taget.value will be the value entered by the user.

Therefore, here we use the ES6 dynamic key syntax to update the corresponding value of the state.

You can now remove the onFirstInputChange and onSecondInputChange event handler methods. We don’t need them anymore.

The completion code is as follows:

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    number1: "".number2: "".result: "".errorMsg: ""
  };

  onInputChange = (event) = > {
    const name = event.target.name;
    const value = event.target.value;
    this.setState({
      [name]: value
    });
  };

  handleAdd = (event) = > {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const sum = number1 + number2;
    if (isNaN(sum)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: sum }); }}; handleSubtract =(event) = > {
    event.preventDefault();
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    const subtraction = number1 - number2;
    if (isNaN(subtraction)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: subtraction }); }};render() {
    return (
      <div>
        <div className="input-section">
          {this.state.errorMsg && (
            <p className="error-msg">{this.state.errorMsg}</p>
          )}
          <label>First Number</label>
          <input
            type="text"
            name="number1"
            placeholder="Enter a number"
            value={this.state.number1}
            onChange={this.onInputChange}
          />
        </div>
        <div className="input-section">
          <label>Second Number</label>
          <input
            type="text"
            name="number2"
            placeholder="Enter a number"
            value={this.state.number2}
            onChange={this.onInputChange}
          />
        </div>
        <div className="result-section">
          Result: <span className="result">{this.state.result}</span>
        </div>
        <button type="button" className="btn" onClick={this.handleAdd}>
          Add
        </button>
        <button type="button" className="btn" onClick={this.handleSubtract}>
          Subtract
        </button>
      </div>); }}Copy the code

3. Use a single calculation method

Now let’s refactor the handleAdd and handleSubtract methods.

We use two separate approaches with nearly identical code to create code duplication. We can solve this problem by creating a single method and passing arguments to functions that identify addition or subtraction operations.

// change the below code:
<button type="button" className="btn" onClick={this.handleAdd}>
  Add
</button>

<button type="button" className="btn" onClick={this.handleSubtract}>
  Subtract
</button>

// to this code:
<button type="button" className="btn" onClick={()= > this.handleOperation('add')}>
  Add
</button>

<button type="button" className="btn" onClick={()= > this.handleOperation('subtract')}>
  Subtract
</button>
Copy the code

Here, we add a new inline method to the onClick handler, in which we manually invoke the new handleOperation method by passing the operation name.

Now add a new handleOperation method and remove the handleAdd and handleSubtract methods you added earlier. As follows:

handleOperation = (operation) = > {
  const number1 = parseInt(this.state.number1, 10);
  const number2 = parseInt(this.state.number2, 10);

  let result;
  if (operation === "add") {
    result = number1 + number2;
  } else if (operation === "subtract") {
    result = number1 - number2;
  }

  if (isNaN(result)) {
    this.setState({
      errorMsg: "Please enter valid numbers."
    });
  } else {
    this.setState({
      errorMsg: "".result: result }); }};Copy the code

The complete code is as follows:

import React from "react";
import "./styles.css";

export default class App extends React.Component {
  state = {
    number1: "".number2: "".result: "".errorMsg: ""
  };

  onInputChange = (event) = > {
    const name = event.target.name;
    const value = event.target.value;
    this.setState({
      [name]: value
    });
  };

  handleOperation = (operation) = > {
    const number1 = parseInt(this.state.number1, 10);
    const number2 = parseInt(this.state.number2, 10);

    let result;
    if (operation === "add") {
      result = number1 + number2;
    } else if (operation === "subtract") {
      result = number1 - number2;
    }

    if (isNaN(result)) {
      this.setState({
        errorMsg: "Please enter valid numbers."
      });
    } else {
      this.setState({
        errorMsg: "".result: result }); }};render() {
    return (
      <div>
        <div className="input-section">
          {this.state.errorMsg && (
            <p className="error-msg">{this.state.errorMsg}</p>
          )}
          <label>First Number</label>
          <input
            type="text"
            name="number1"
            placeholder="Enter a number"
            value={this.state.number1}
            onChange={this.onInputChange}
          />
        </div>
        <div className="input-section">
          <label>Second Number</label>
          <input
            type="text"
            name="number2"
            placeholder="Enter a number"
            value={this.state.number2}
            onChange={this.onInputChange}
          />
        </div>
        <div className="result-section">
          Result: <span className="result">{this.state.result}</span>
        </div>
        <button
          type="button"
          className="btn"
          onClick={()= > this.handleOperation("add")}
        >
          Add
        </button>
        <button
          type="button"
          className="btn"
          onClick={()= > this.handleOperation("subtract")}
        >
          Subtract
        </button>
      </div>); }}Copy the code

4. Deconstruct grammar using ES6

In the onInputChange method, we have the following code:

const name = event.target.name;
const value = event.target.value;
Copy the code

We can simplify it using the ES6 deconstruction syntax, as follows:

const { name, value } = event.target;
Copy the code

Here, we extract the name and value properties, event.target, from the object and create local name and value variables to store these values.

Now, instead of referring to the state this.state.number2 every time we use this.state.number1 and inside the handleOperation method, we can isolate these variables beforehand:

// change the below code:

const number1 = parseInt(this.state.number1, 10);
const number2 = parseInt(this.state.number2, 10);

// to this code:

let { number1, number2 } = this.state;
number1 = parseInt(number1, 10);
number2 = parseInt(number2, 10);
Copy the code

5. Use enhanced object literal syntax

If you examine the function call handleOperation inside the setState function, it looks like this:

this.setState({
  errorMsg: "".result: result
});
Copy the code

We can simplify this code by using enhanced object literal syntax.

If the attribute name matches the variable name exactly, result: result then we can skip the part mentioned after the colon. Therefore, the setState function call above can be simplified as follows:

this.setState({
  errorMsg: "",
  result
});
Copy the code

6. Convert the class component to React Hooks

As of React version 16.8.0, React has added a way to use state and lifecycle methods inside function components using React Hooks.

** Using React Hooks allows us to write code that is much shorter and easier to maintain and understand. ** So let’s convert the code above to use React Hooks syntax.

If you are not familiar with React Hooks, check out the Introduction to React Hooks.

First let’s declare the App component as a function component:

const App = () = >{};export default App;
Copy the code

To declare state, we need to use the useState hook, so import it to the top of the file. Then create three Usestates, one for storing the numbers together as objects. We can use a handler function and two other useState calls to update them together to store results and error messages.

import React, { useState } from "react";

const App = () = > {
  const [state, setState] = useState({
    number1: "".number2: ""
  });
  const [result, setResult] = useState("");
  const [errorMsg, setErrorMsg] = useState("");
};

export default App;
Copy the code

Change the onInputChange handler method to:

const onInputChange = () = > {
  const { name, value } = event.target;

  setState((prevState) = > {
    return {
      ...prevState,
      [name]: value
    };
  });
};
Copy the code

Here we use the updater syntax to set the state, because when using React Hooks, the state is not merged automatically when updating objects.

Therefore, we first disperse all the attributes of the state, and then add new state values.

Change the handleOperation method to this:

const handleOperation = (operation) = > {
  let { number1, number2 } = state;
  number1 = parseInt(number1, 10);
  number2 = parseInt(number2, 10);

  let result;
  if (operation === "add") {
    result = number1 + number2;
  } else if (operation === "subtract") {
    result = number1 - number2;
  }

  if (isNaN(result)) {
    setErrorMsg("Please enter valid numbers.");
  } else {
    setErrorMsg(""); setResult(result); }};Copy the code

Complete code:

import React, { useState } from "react";
import "./styles.css";

const App = () = > {
  const [state, setState] = useState({
    number1: "".number2: ""
  });
  const [result, setResult] = useState("");
  const [errorMsg, setErrorMsg] = useState("");

  const onInputChange = (event) = > {
    const { name, value } = event.target;

    setState((prevState) = > {
      return {
        ...prevState,
        [name]: value
      };
    });
  };

  const handleOperation = (operation) = > {
    let { number1, number2 } = state;
    number1 = parseInt(number1, 10);
    number2 = parseInt(number2, 10);

    let result;
    if (operation === "add") {
      result = number1 + number2;
    } else if (operation === "subtract") {
      result = number1 - number2;
    }

    if (isNaN(result)) {
      setErrorMsg("Please enter valid numbers.");
    } else {
      setErrorMsg(""); setResult(result); }};return (
    <div>
      <div className="input-section">
        {errorMsg && <p className="error-msg">{errorMsg}</p>}
        <label>First Number</label>
        <input
          type="text"
          name="number1"
          placeholder="Enter a number"
          value={state.number1}
          onChange={onInputChange}
        />
      </div>
      <div className="input-section">
        <label>Second Number</label>
        <input
          type="text"
          name="number2"
          placeholder="Enter a number"
          value={state.number2}
          onChange={onInputChange}
        />
      </div>
      <div className="result-section">
        Result: <span className="result">{result}</span>
      </div>
      <button
        type="button"
        className="btn"
        onClick={()= > handleOperation("add")}
      >
        Add
      </button>
      <button
        type="button"
        className="btn"
        onClick={()= > handleOperation("subtract")}
      >
        Subtract
      </button>
    </div>
  );
};

export default App;
Copy the code

7. Return objects implicitly

At this point, we’ve optimized our code to use modern ES6 features and avoid code duplication. Another thing we can do is simplify the setState function call.

If the current setState function in the check handler calls onInputChange, it looks like this:

setState((prevState) = > {
  return {
    ...prevState,
    [name]: value
  };
});
Copy the code

In the arrow function, if we have code like this:

const add = (a, b) = > {
 return a + b;
}
Copy the code

Then we can simplify it as follows:

const add = (a, b) = > a + b;

Copy the code

This works because if there is only one statement in the body of the arrow function, we can skip the curly braces and return keyword. This is called implicit return.

So, if we were to return an object like this from the arrow function:

const getUser = () = > {
 return {
  name: 'David, age: 35 } }Copy the code

We can’t simplify it like this:

const getUser = () = > {
  name: 'David, age: 35 }Copy the code

Because braces indicate the start of a function, the above code is invalid. To make it work, we can wrap the object in parentheses, as follows:

const getUser = () = > ({
  name: 'David, age: 35 })Copy the code

The above code is equivalent to the following:

const getUser = () = > {
 return {
  name: 'David, age: 35 } }Copy the code

Therefore, we can use the same technique to simplify setState function calls.

setState((prevState) = > {
  return {
    ...prevState,
    [name]: value
  };
});

// the above code can be simplified as:

setState((prevState) = > ({
  ...prevState,
  [name]: value
}));
Copy the code

This technique of wrapping code in square brackets is often used in React:

  • Define functional components:
const User = () = > (
   <div>
    <h1>Welcome, User</h1>
    <p>You're logged in successfully.</p>
   </div>
);
Copy the code
  • Inside the mapStateToProps function in react-redux:
const mapStateToProps = (state, props) = > ({ 
   users: state.users,
   details: state.details
});
Copy the code
  • Redux action creator:
const addUser = (user) = > ({
  type: 'ADD_USER',
  user
});
Copy the code

8. Additional tips for writing a better React component

If we have components like this:

const User = (props) = > (
   <div>
    <h1>Welcome, User</h1>
    <p>You're logged in successfully.</p>
   </div>
);
Copy the code

Then you want to test or debug it only, rather than convert the code to the following:

const User = (props) = > {
 console.log(props);
 return (
   <div>
    <h1>Welcome, User</h1>
    <p>You're logged in successfully.</p>
   </div>
 );
}
Copy the code

Can | | using the logical OR operator () like this:

const User = (props) = > console.log(props) || (
   <div>
    <h1>Welcome, User</h1>
    <p>You're logged in successfully.</p>
   </div>
);
Copy the code

Only print to the console. The log function its value, but don’t return any content – it will be evaluated for undefined | | (…). .

And because the | | operator returns the first real value, so the code after | | will be executed.