Write clean React code: starting with JSX
React is arguably the most popular JavaScript rending library/framework right now. With its rising popularity and now dominating the job market, many similar JavaScript rendering libraries followed suit and adopted JSX into their work. In case you are new to frontend development, JSX is a JavaScript Syntax introduced as part of React (open sourced by Facebook back in 2015). Now you can find JSX in not only projects that are meant to be similar to React (e.g. Preact) but also things like Nerv, infernojs, Deku, and etc.
If we just look at the downloads in the past 6 months, you can see these projects have a reason to believe React and JSX is loved by the community. Even with the rise of Vue, it is still destroying the competition at the moment.
I have been using React for almost 4 years now, in projects anything between personal side projects to large scale commercial products at work. So then why am I saying we need something that's not JSX? Before we answer this question let's take a look at the syntax itself first.
The JSX syntax
JSX is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like. JSX may remind you of a template language, but it comes with the full power of JavaScript. - React Documentation
That might be a bit vague, so here's an example.
// a function component AKA dumb component
const myComponent = ({ title, description}) => {
const title = 'my title';
const description = 'a simple description';
const list = [
{ name: 'item 1' },
{ name: 'item 2' },
{ name: 'item 3' },
{ name: 'item 4' },
];
const listComponents = list.forEach(listItem => (
<li>{listItem}</li>
));
return (
<div>
<h1>{title}</h1>
<p>{description}</p>
{listComponents}
</div>
);
};
If you used React before then you'll know the above example is a simple Function component that displays a title, description as well as a short list. Of course, this is just a very simple example and there are other ways we could create components in React. In this example, it is simple enough that there isn't any opportunity to add in any ugly code. In the next part, we'll look at some common mistakes people make.
Common mistakes
Personally, I'm not a big fan of JSX or what I prefer to call "HTML-in-JS". Even if the tools support solved all the issues from a few years ago, such as weird linting errors, auto-format breaking indentation and etc. What makes it worse is the "flexibility" JSX offers, and unlike Vue or Angular you can seriously write some horrible code that is perfectly functional just impossible to read.
Passing Props
React is all about componentising the application, so everything is in chunk size files which means separation of logic and concern. With this componentisation, what we also have is the issue of passing data around, especially from parent component to their children. When done wrong can be a serious pain to look at.
Check out this example below. This is what happens when a child component requires a lot of prop data from its parent, and all these data are passed down as individual attributes on the component tag.
return (
<div>
<h1>{title}</h1>
<myUglyComponent
user={user}
date={date}
isActive={isActive}
shouldDisplay={shouldDisplay}
selectedDate={selectedDate}
options={options}
activePeriod={activePeriod}
propertyOne={propertyOne}
propertyTwo={propertyTwo}
propertyThree={propertyThree}
propertyFour={propertyFour}
propertyFive={propertyFive}
/>
</div>
)
By the way, this is nowhere close to the worst I've seen people do. No joke, I've seen child component with as many as 20 props passed to them. Also, there are multiple of these children components in the same file. To me this is terrible, and we should avoid dumping so much unnecessary data into the return function. It is JSX's fault for allowing this in the first place, but we should know the difference between "okay amount of attributes" and "too damn many to see without scrolling".
Please just create a variable before the return or render and pass in as one prop value instead, see the example below.
const data = {
user,
date,
isActive,
shouldDisplay,
selectedDate,
options,
activePeriod,
...
};
return (
<div>
<h1>{title}</h1>
<myUglyComponent data={data} />
</div>
);
I hope everyone agrees that this is much easier to read. People might ask "what about the proptype checking?", well it is no different to proptype checking than other objects passed into a component. Below is an example of proptype checking for nested objects.
import PropTypes from 'prop-types';
propTypes: {
data: PropTypes.shape({
user: PropTypes.shape({
...
}),
date: PropTypes.date.isRequired,
isActive: PropTypes.bool,
...
})
}
Conditional (ternary) operator
I love using ternaries, they are concise and to the point. But when used wrongly, they could make the code confusing to read even in plain JavaScript not even mentioning JSX. Now let's see how we can break the spirit of the next person looking at our code.
return (
<div>
<h1>{title}</h1>
{
isValidComponent
? <ComponentOne />
: <Error />
}
{ shouldDisplay && <ComponentTwo /> }
</div>
)
Did you find the above example difficult to understand? Probably not, again this is a simple example. But imagine a component with a few of these in the return logic. I don't think many will argue for this kind of approach. Ternaries should only be used if it doesn't sacrifice readability if anything it should improve it.
Just for fun, here's something else I've seen before, that should only exist in nightmares.
return (
<div>
<h1>{title}</h1>
{
validData
? (
displaytype
? <ComponentOne />
: <ComponentTwo />
)
: <Error />
}
{ shouldDisplay && <ComponentThree /> }
</div>
)
It is not even difficult to fix. Just like the part, we talked about passing props, let's move some of the logic outside of the return and then see.
const renderComponentOne = if (validData && displaytype) <ComponentOne />;
const renderComponentTwo = if (validData && !displaytype) <ComponentTwo />;
const renderComponentThree = shouldDisplay && <ComponentThree />;
const renderError = if (!validData) <ComponentThree />;
return (
<div>
<h1>{title}</h1>
{ renderComponentOne }
{ renderComponentTwo }
{ renderComponentThree}
{ renderError }
</div>
)
Deal with complex business logic
Often due to product requirements, we need to add in logic to handle specific cases and scenarios. Over time, this logic would build on top of each other and can become difficult to maintain.
I think it is always important to remember, a component is meant to be isolated from other parts of the application. But this doesn't mean we cannot break a complex component down into multiple files.
Ultilities/Helper Files
There is nothing wrong with having multiple utilities or helper files in a project, not only do we abstract out common code. I have seen both projects maintaining a single file approach to avoid duplication of logic, but I also worked on projects where the team preferred many single logic helper files. It doesn't matter which of these approaches teams go with, the important thing here is that the logic is abstracted out from the over-complicated component.
These helpers will (or should) always be just JavaScript code. This means it is super easy to unit test these functions.
High-Order Components (HOC)
We could have a component that only handles basic presentational logic e.g. render a component. Then we can create a High-Order Function (HOC) component to handle the complex product requirements. This way we have cleaner components, and also this also makes testing a lot easier with this abstraction.
// Higher Order Component
const hoc = (WrappedComponent) => (props) => (
<React.Fragment>
<WrappedComponent {...props}>
£{props.children}
</WrappedComponent>
</React.Fragment>
);
// Component
const Price = (props) => <span>{props.children}</span>;
// Call HOC
const ConvertedPrice = hoc(Price);
// App
const App = () => <ConvertedPrice>17.00</ConvertedPrice>;
ReactDOM.render(
<App />,
document.getElementById('root')
);
See CSS Tricks: What are HOC for more information.