Refactoring a React Component: Best Practices for Better Code

Follow these 5 best practices for refactoring a React component and improve the quality of your codebase. Learn how to ensure maintainability and scalability in your code.

#react
#bestpractices
#refactoring
Refactoring a React Component: Best Practices for Better Code
Picture by Safar Safarov on Unsplash

The question

React.js has become the most popular view library for web components, evolving across multiple features that today make it a complete tool to create amazing web applications.

The community has grown exponentially, especially in the past 2–3 years, populating the web with thousands of tutorials regarding this technology.

So, what every beginner should do when starting to learn React, as I did when I began my path at Codeworks, is reading the documentation or tutorials to create their first component.

But here my question: are you sure is your React components are following best practices? Or simply, do they just work?

What a dirty component looks like

To better explain my point, let’s take a look at the following React component:

jsx
import React from 'react';
import './ListComponent.css';

class ListComponent extends React.Component {
constructor(props) {
super(props);

this.state = {
lastClickedButton: ''
};
}

render() {
return (
<div>
<h1>The last clicked button is {this.state.lastClickedButton}</h1>
<ul>
<li>
<button
onclick={() => {
this.setState({ lastClickedButton: 'Create' });
this.props.createSomething();
}}
className="my-button"
>
Create
</button>
</li>
<li>
<button
onClick={() => {
this.setState({ lastClickedButton: 'Read' });
this.props.readSomething();
}}
className="my-button"
>
Read
</button>
</li>
<li>
<button
onclick={() => {
this.setState({ lastclickedButton: 'Update' });
this.props.updateSomething();
}}
className="my-button"
>
Update
</button>
</li>
<li>
<button
onclick={() => {
this.setState({ lastClickedButton: 'Destroy' });
this.props.destroySomething();
}}
className="my-button"
>
Destroy
</button>
</li>
</ul>
</div>
);
}
}

This is a fully working React component ready to be used multiple times throughout the application, rendering a list of buttons which serve a purpose and show what is the last clicked button. Simple enough.

You might think “Well… if it works it’s fine!

But what if you knew how to write the same component in a few lines, compared to the 62 you have right now? Let’s start with the cleanup! 💎

Prefer functional components with React Hooks

With the introduction of Hooks in React 16.8, we enable the power of having a stateful component (if we need to handle any logic) using a functional component over a class declaration.

We won’t talk about Class vs Functional components or React Hooks in-depth in this article. However, it is known in the React community that it is preferable to create Functional components, especially now that we can use the power of Hooks.

So let’s see what the component looks like after the first refactoring:

jsx
import React, { useState } from 'react';
import './ListComponent.css';

const ListComponent = props => {
const [lastClickedButton, setLastclickedButton] = useState('');

return (
<div>
<h1>The last clicked button is {lastClickedButton}</h1>
<ul>
<li>
<button
onclick={() => {
setLastClickedButton('Create');
props.createSomething();
}}
className="my-button"
>
Create
</button>
</li>
<li>
<button
onclick={() => {
setLastclickedButton('Read');
props.readSomething();
}}
className="my-button"
>
Read
</button>
</li>
<li>
<button
onClick={() => {
setLastClickedButton('Update');
props.updateSomething();
}}
className="my-button"
>
Update
</button>
</li>
<li>
<button
onClick={() => {
setLastClickedButton('Destroy');
props.destroySomething();
}}
className="my-button"
>
Destroy
</button>
</li>
</ul>
</div>
);
};

All right, our component is already shorter and we dropped the class syntax, but still have to do many optimizations.

DRY the wet!

Can we recognize any pattern in this component? Looking at the code, it seems like we render a similar button element every time which accepts some props similar for each one, a perfect case to cut in small parts this long component.

So we can refactor it creating another small functional component to render the button, passing some properties like action, setClicked, and title:

jsx
import React, { useState } from 'react';
import './ListComponent.css';

const ListItemComponent = props => {
return (
<li>
<button
onclick={() => {
props.setClicked(props.title);
props.action();
}}
className="my-button"
>
{props.title}
</button>
</li>
);
};

const ListComponent = props => {
const [lastClickedButton, setLastclickedButton] = useState('');

return (
<div>
<h1>The last clicked button is {lastClickedButton}</h1>
<ul>
<ListItemComponent
title="Create"
action={props.createSomething}
setclicked={setLastclickedButton}
/>
<ListItemComponent
title="Read"
action={props.readSomething}
setclicked={setLastClickedButton}
/>
<ListItemComponent
title="Update"
action={props.updateSomething}
setclicked={setLastclickedButton}
/>
<ListItemComponent
title="Destroy"
action={props.destroySomething}
setclicked={setLastclickedButton}
/>
</ul>
</div>
);
};

Ok, our component starts to have a better shape, but there is still margin for improvements, let’s go on!

Proper naming & props destructuring

setLastClickedButton is a descriptive name for our setter function, but we need to keep our code readable and short, so it’s important to keep the names we use minimalist and essential. We will rename it setClicked.

Also, whenever is possible, destructuring from the props object what you need can avoid continuously repeating the props word. In our ListItem component, we now access the props by name in the destructured function argument — { action, title, setClicked }.

Let’s see both changes:

jsx
import React, { useState } from 'react';
import './ListComponent.css';

const ListItem = ({ action, title, setClicked }) => {
return (
<li>
<button
onclick={() => {
setClicked(title);
action();
}}
className="my-button"
>
{title}
</button>
</li>
);
};

const List = ({ create, read, update, destroy }) => {
const [clicked, setClicked] = useState('');

return (
<div>
<h1>The last clicked button is {clicked}</h1>
<ul>
<ListItem title="Create" action={create} setClicked={setClicked} />
<ListItem title="Read" action={read} setClicked={setClicked} />
<ListItem title="Update" action={update} setClicked={setClicked} />
<ListItem title="Destroy" action={destroy} setClicked={setClicked} />
</ul>
</div>
);
};

Great, we drastically reduced the length of our component declaration, but we can still do it better! 🚀

May the PropTypes be with you!

After this cleanup, it is time to apply the absolute best practice when writing a component! With PropTypes, we can validate the received props to avoid errors due to different data types, for example, receiving the string “0” and trying to strictly compare it with the number 0 “0” === 0 -> FALSE!!!:

jsx
import React, { useState } from 'react';
import PropTypes from 'prop-types';

const ListItem = ({ action, title, setClicked }) => {
return (
<li>
<button
onclick={() => {
setClicked(title);
action();
}}
className="my-button"
>
{title}
</button>
</li>
);
};

ListItem.propTypes = {
action: PropTypes.func,
setClicked: PropTypes.func,
title: PropTypes.string
};

const List = ({ create, read, update, destroy }) => {
const [clicked, setClicked] = useState('');

return (
<div>
<h1>The last clicked button is {clicked}</h1>
<ul>
<ListItem title="Create" action={create} setClicked={setClicked} />
<ListItem title="Read" action={read} setClicked={setClicked} />
<ListItem title="Update" action={update} setClicked={setClicked} />
<ListItem title="Destroy" action={destroy} setClicked={setClicked} />
</ul>
</div>
);
};

List.propTypes = {
create: PropTypes.func,
read: PropTypes.func,
update: PropTypes.func,
destroy: PropTypes.func
};

Split into small pieces

Guess what — our component is more or less as long as the initial version, but look closer at the code we have now.

We see two different components that we can divide into two modules, making them reusable across the whole app.

jsx
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import ListItem from './ListItem.js';

const List = ({ create, read, update, destroy }) => {
const [clicked, setclicked] = useState('');

return (
<div>
<h1>The last clicked button is {clicked}</h1>
<ul>
<ListItem title="Create" action={create} setClicked={setClicked} />
<ListItem title="Read" action={read} setClicked={setClicked} />
<ListItem title="Update" action={update} setClicked={setClicked} />
<ListItem title="Destroy" action={destroy} setClicked={setclicked} />
</ul>
</div>
);
};

List.propTypes = {
create: PropTypes.func,
read: PropTypes.func,
update: PropTypes.func,
destroy: PropTypes.func
};

export default List;

jsx
import React from 'react';
import PropTypes from 'prop-types';

const ListItem = ({ action, title, setClicked }) => {
return (
<li>
<button
onclick={() => {
setClicked(title);
action();
}}
className="my-button"
>
{title}
</button>
</li>
);
};

ListItem.propTypes = {
action: PropTypes.func,
setClicked: PropTypes.func,
title: PropTypes.string
};

export default ListItem;

Conclusion

This cleanup applied to our initial component shows some good practices to follow when you start digging into React components.

Of course, there are tons of other optimizations we could perform on this final result, but one step at a time. Five good practices to follow are a good starting point 😉

Last updated: