How to Write Better React Components
JavaScript added many useful features starting with ES6 and nd these changes help developers write code that is short and easy to understand and maintain.
When you use create-react-app to create a React App, you already have support for these changes. This is because it uses Babel.js to convert the ES6+ code to ES5 code which all browsers understand.
In this article, we'll explore various ways we can write shorter, simpler and easier to understand React code. So let's get started.
Take a look at the this Code Sandbox demo.
Here, we have two input text boxes that take input from users, and two buttons that calculate the addition and subtraction of the numbers provided as input.
Avoid manually binding event handlers
As you know in React, when we attach any onClick
or onChange
or any other event handler like this:
<input
...
onChange={this.onFirstInputChange}
/>
then, the handler function (onFirstInputChange) does not retain the binding of this
.
This is not an issue with React, but that's how JavaScript event handlers work.
So we have to use the .bind
method to correctly bind this
like this:
constructor(props) {
// some code
this.onFirstInputChange = this.onFirstInputChange.bind(this);
this.onSecondInputChange = this.onSecondInputChange.bind(this);
this.handleAdd = this.handleAdd.bind(this);
this.handleSubtract = this.handleSubtract.bind(this);
}
The above lines of code will maintain this
's binding of the class correctly inside the handler functions.
But adding a new binding code for every new event handler is tedious. Luckily we can fix it using the class properties syntax.
Using class properties allows us to define properties directly inside the class.
Create-react-app internally uses the @babel/babel-plugin-transform-class-properties
plugin for Babel version >= 7 and babel/plugin-proposal-class-properties
plugin for Babel version <7 Â so you don't have to manually configure it.
To use it, we need to convert the event handler functions to arrow function syntax.
onFirstInputChange(event) {
const value = event.target.value;
this.setState({
number1: value
});
}
The above code can be written as follows:
onFirstInputChange = (event) => {
const value = event.target.value;
this.setState({
number1: value
});
}
In a similar way, we can convert the other three functions:
onSecondInputChange = (event) => {
// your code
}
handleAdd = (event) => {
// your code
}
handleSubtract = (event) => {
// your code
}
Also, there is no need to bind the event handlers in the constructor. So we can remove that code. Now the constructor will look like this:
constructor(props) {
super(props);
this.state = {
number1: "",
number2: "",
result: "",
errorMsg: ""
};
}
We can simplify it even further. The class properties syntax allows us to declare any variable directly inside the class so we can completely remove the constructor and declare the state as a part of the class, as shown below:
export default class App extends React.Component {
state = {
number1: "",
number2: "",
result: "",
errorMsg: ""
};
render() { }
}
Here's a Code Sandbox demo: https://codesandbox.io/s/trusting-dust-ukvx2
If you check out the above Code Sandbox demo, you will see that the functionality is still working as before.
But using class properties makes the code much simpler and easy to understand.
Nowadays, you will find React code written like this.
Use a single event handler method
If you inspect the above code, you will see that for each input field we have a separate event handler function, onFirstInputChange
and onSecondInputChange
.
If the number of input fields increases, the number of event handler functions also increases, which is not good.
For example, if you're creating a registration page, then there will be many input fields. So creating a separate handler function for each input field is not feasible.
Let's change that.
To create a single event handler that will handle all input fields, we need to give a unique name to each input field that matches exactly with the corresponding state variable names.
We already have this setup. The names number1
and number2
which we've given to the input fields are also defined in the state. So let's change the handler method of both of the input fields to onInputChange
like this:
<input
type="text"
name="number1"
placeholder="Enter a number"
onChange={this.onInputChange}
/>
<input
type="text"
name="number2"
placeholder="Enter a number"
onChange={this.onInputChange}
/>
and add a new onInputChange
event handler like this:
onInputChange = (event) => {
const name = event.target.name;
const value = event.target.value;
this.setState({
[name]: value
});
};
Here, while setting the state, we're setting the dynamic state name with the dynamic value. So when we're changing the number1
input field value, event.target.name
will be number1
and event.target.value
will be the user-entered value.
And when we're changing the number2
input field value, event.target.name
will be number2
and event.taget.value
will be the user-entered value.
So here we're using the ES6 dynamic key syntax to update the corresponding value of the state.
Now you can delete the onFirstInputChange
and onSecondInputChange
event handler methods. We no longer need them.
Here's a Code Sandbox demo: https://codesandbox.io/s/withered-feather-8gsyc
Use a single calculation method
Now, let's refactor the handleAdd
and handleSubtract
methods.
We're using two separate methods that have almost the same code which creates code duplication. We can fix this by creating a single method and passing a parameter to the function identifying the addition or subtraction operation.
// 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>
Here, we've added a new inline method for the onClick
handler where we're manually calling a new handleOperation
method by passing the operation name.
Now, add a new handleOperation
method like this:
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
});
}
};
and remove the previously added handleAdd
and handleSubtract
methods.
Here's a Code Sandbox demo: https://codesandbox.io/s/hardcore-brattain-zv09d
Use ES6 destructuring syntax
Inside the onInputChange
method, we have code like this:
const name = event.target.name;
const value = event.target.value;
We can use ES6 destructuring syntax to simplify it like this:
const { name, value } = event.target;
Here, we're extracting the name
and value
properties from the event.target
object and creating local name
and value
variables to store those values.
Now, inside the handleOperation
method, instead of referring to state every time we use  this.state.number1
and this.state.number2
, we can separate out those 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);
Here's a Code Sandbox demo: https://codesandbox.io/s/exciting-austin-ldncl
Use enhanced object literal syntax
If you check the setState
function call inside the handleOperation
function, it looks like this:
this.setState({
errorMsg: "",
result: result
});
We can use the enhanced object literal syntax to simplify this code.
If the property name exactly matches with the variable name like result: result
then we can skip mentioning the part after the colon. So the above setState
function call can be simplified like this:
this.setState({
errorMsg: "",
result
});
Here's a Code Sandbox demo: https://codesandbox.io/s/affectionate-johnson-j50ks
Convert class components to React Hooks
Starting with React version 16.8.0, React has added a way to use state and lifecycle methods inside the functional components using React Hooks.
Using React Hooks allows us to write a code that is a lot shorter and easy to maintain and understand. So let's convert the above code to use React Hooks syntax.
If you're new to React Hooks, check out my introduction to react hooks article.
Let's first declare an App component as a functional component:
const App = () => {
};
export default App;
To declare the state we need to use the useState
hook, so import it at the top of the file. Then create 3 useState
calls, one for storing the numbers together as an object. We can update them together using a single handler function and two other useState
calls for storing the result and error message.
import React, { useState } from "react";
const App = () => {
const [state, setState] = useState({
number1: "",
number2: ""
});
const [result, setResult] = useState("");
const [errorMsg, setErrorMsg] = useState("");
};
export default App;
Change the onInputChange
handler method to this:
const onInputChange = () => {
const { name, value } = event.target;
setState((prevState) => {
return {
...prevState,
[name]: value
};
});
};
Here, we're using the updater syntax for setting the state because, when working with React Hooks, the state is not merged automatically when updating an object.
So we're first spreading out all the properties of the state and then adding the new state value.
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);
}
};
Now, return the same JSX returned from the render method of the class component but remove all the references of this
and this.state
from the JSX.
Here's a Code Sandbox demo: https://codesandbox.io/s/musing-breeze-ec7px?file=/src/App.js
Implicitly return objects
Now, we have optimized our code to use modern ES6 features and avoided code duplications. There is one more thing we can do is to simplify the setState
function call.
If you check the current setState
function call inside the onInputChange
handler, it looks like this:
setState((prevState) => {
return {
...prevState,
[name]: value
};
});
In an arrow function, if we have code like this:
const add = (a, b) => {
return a + b;
}
Then we can simplify it as shown below:
const add = (a, b) => a + b;
This works because If there is a single statement in the arrow function body, then we can skip the curly brackets and the return keyword. This is known as an implicit return.
So if we're returning an object from arrow function like this:
const getUser = () => {
return {
name: 'David,
age: 35
}
}
Then we can't simplify it like this:
const getUser = () => {
name: 'David,
age: 35
}
This is because opening curly brackets indicates the beginning of the function, so the above code is invalid. To make it work we can wrap the object in round brackets like this:
const getUser = () => ({
name: 'David,
age: 35
})
The above code is the same as the below code:
const getUser = () => {
return {
name: 'David,
age: 35
}
}
So we can use the same technique to simplify our setState
function call.
setState((prevState) => {
return {
...prevState,
[name]: value
};
});
// the above code can be simplified as:
setState((prevState) => ({
...prevState,
[name]: value
}));
Here's a Code Sandbox demo: https://codesandbox.io/s/sharp-dream-l90gf?file=/src/App.js
This technique of wrapping code in round brackets is used a lot in React:
- To define a functional component:
const User = () => (
<div>
<h1>Welcome, User</h1>
<p>You're logged in successfully.</p>
</div>
);
- Inside mapStateToProps function in react-redux:
const mapStateToProps = (state, props) => ({
users: state.users,
details: state.details
});
- Redux action creator functions:
const addUser = (user) => ({
type: 'ADD_USER',
user
});
and many other places.
If we have a component like this:
const User = (props) => (
<div>
<h1>Welcome, User</h1>
<p>You're logged in successfully.</p>
</div>
);
and later want to log the props to the console just for testing or debugging, then instead of converting the code to the below code:
const User = (props) => {
console.log(props);
return (
<div>
<h1>Welcome, User</h1>
<p>You're logged in successfully.</p>
</div>
);
}
you can use 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>
);
How does it work?
The console.log
function just prints the value passed to it, but it does not return anything – so it will be evaluated as undefined ||
(...).
And because the ||
operator returns the first truthy value, the code after ||
will also be executed.
Thanks for reading!
Check out my recently published Mastering Redux course.
In this course, you will build 3 apps along with the food ordering app and you'll learn:
- Basic and advanced Redux
- How to manage the complex state of array and objects
- How to use multiple reducers to manage complex redux state
- How to debug Redux application
- How to use Redux in React using react-redux library to make your app reactive.
- How to use redux-thunk library to handle async API calls and much more
and then finally we'll build a complete food ordering app from scratch with stripe integration for accepting payments and deploy it to the production.
Want to stay up to date with regular content regarding JavaScript, React, Node.js? Follow me on LinkedIn.
Great article, thanks! Your article helped me a lot for my college project, I thought for a long time about how to optimize the application code. In order to free up more time for the project, I turned to the https://edubirdie.com/write-my-essay-for-me service so that they would take on part of the work. It was much more important for me to finish my technical project than to write an essay, so their services came in handy.
Oh nice Yogesh, I live your blog.
https://cinehub.me/
Thank you 🙂