In recent years, React's popularity has skyrocketed as it is used to create dynamic and interactive websites and web apps. In this blog post, you will find a comprehensive list of the best practices to keep in mind when developing React JS applications. This post will cover topics such as styling, file structure, and performance optimization. Let's get started!
Must Follow Rules
These are the most common, every project, even the smaller ones should follow those practices for a couple of reasons:
- They require no/very little time to implement them.
- They are lifesavers for your time and organization.
Let the editor organize the module imports
Let's be honest, we love following the "best" for everything, even the only world "best" is grateful for some of us, and staying on topic, one of the BEST way to lose time is to think about the module import order.
This is a super common, day-to-day operation that we do 10000 times, so it's better to let the editor handle this, simply.
{
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.sortMembers": true
}
}
Pay attention that the organizeImports
will also delete your unused imports,
if you want to keep them just use this variant.
{
"editor.codeActionsOnSave": {
"source.sortImports": true,
"source.sortMembers": true
}
}
Best Practices Dealing With Components
Components are the foundation of React and like buildings, bad foundations, terrible results, let's see some of them.
Use and for the Sake of God keep one naming convention
As developers, we always love to add our personal touch to our work, but sometimes things get out of hand. Now, I will ask you to imagine a component name for displaying a list of articles, that component will be added to the example project below.
src/
components/
article/
article-title.js
Article.jsx
Button/
index.js
button.js
NavBar/
NavBar.jsx
Header.jsx
menu-bar/
menu.jsx
index.js
So which name would you choose? You can agree with me that it's kinda difficult to have a "conventional" choice here, due to a reckless naming convention.
For the record, if you have never seen something like this, know that it happens much more often than you think...
Following one naming convention can save you a lot of brain energy thinking about how to name your next component. You will feel like Zuckerberg choosing the same gray t-shirt from the wardrobe every day.
Keep Related Components Close to Each Other
While building the component logic, it's almost useless to start thinking about how the component will be used for 10 months. It is ok to do that, but, with common sense, you read about it before.
That's why is better for your mental sanity to keep the related component close to each other in terms of directories or files. Take a look at this example.
src/
components/
common/
Button/
index.js
Button.js
Button.spec.js
Hero/
index.js
Hero.js
HeroImage.js
In this case, the Hero Image
can be a simple image component with some useful
props for faster development. Why is this not in the common folder? Because no
one except for Hero
(yet, and potentially forever) is using that component.
Once you will discover that needing, you can abstract it into an Image
component and use it two times as well. I assure you the code will not be
offended.
Split the Component if it Grows Too Much
What's the ideal size of a component? Well nobody knows, it depends.
Ideally when you start feeling some difficulties moving through the file may be it's time to start to think about splitting it.
If you are experienced enough you can normally already reason is divided components without starting from a monolith, but if you are a novice the good rule, "first do, then optimize" will be your beacon in the night.
Here's an ordered list of what you can look for splitting:
- External component functions.
- Stateless components.
- Refactoring stateful logic using custom hooks.
- Stateful components.
Avoid Useless DOM Nodes Calculation
Every DOM node has a cost of rendering, and if your page grows, your dear friend Lighthouse can warn you about avoiding an excessive DOM size, and you should reduce or optimize the number of rendered nodes.
One thing I commonly see is the abuse of the CSS visibility attributes over conditional rendering, I'm talking about this:
const Pokemon = ({ data }) => {
const [showMoves, setShowMoves] = React.useState(false);
return (
<Box border="1px solid #cccccc">
<h2>{data.name}</h2>
<Box padding={12}>
<Moves data={data.moves} display={showMoves ? "block" : "none"} />
<Toggle onToggle={showMoves} />
</Box>
</Box>
);
};
Now, most of the time this will be not a real problem. But in case the Moves
the component would be huge, with a lot of sub-nodes you should consider to
handle its visibility using conditional rendering. Your browser will avoid
parsing and constructing the DOM.
const Pokemon = ({ data }) => {
const [showMoves, setShowMoves] = React.useState(false);
return (
<Box border="1px solid #cccccc">
<h2>{data.name}</h2>
<Box padding={12}>
{showMoves && <Moves data={data.moves} />}
<Toggle onToggle={showMoves} />
</Box>
</Box>
);
};
And yes,
you can use the &&
operator
if you don't mess up the state type.
An extra tip is: use Fragments instead of divs if you need to force a container for your components, it will be excluded from rendering.
Join The Front-End Club
Here, you'll find all the latest and greatest updates in the world of Front-End Development. From new technologies and best practices, to tips and tricks for improving your skills.
Best Practices Dealing With Props
If components are the foundation, props are their children, and we should thread them well too.
Do Not Exceed with Props Number
Let's analyze a piece of code.
const Page = ({ data }) => {
return (
<PageHero
mainImage={data.backgroundImage}
pictures={data.images.map((image) => image.filename)}
youtubeVideo={data.youtubeVideo}
tour360={data.tour360}
title={data.title}
status={data.status}
contractType={data.contractType}
itemId={data.id}
trakingId={data.trackingId}
/>
{/** Continues... */}
);
};
This is a hero of a page, looking at it I feel so confused about how this component works... it should be a hero, why does it have so many props?
Furthermore, a lot of them seem to be external to it. You don't know it, but I can tell you that in that case, the title was in reality the "alt tag" of the "mainImage", and the "trackingId" was the id of a tracking service called triggering a click on one specific button inside this hero.
This is what is commonly called "Props Hell", the idea here is that you start losing the way this component is working, and you solve the new request putting every time this new prop as an extra layer, no matter how many subs components are present in the tree.
I don't need to explain how in the long run this leads to mental insanity, simply in this case it is good to divide the monolith into several parts and assign properties more consistently accordingly.
Destructure the Props Object
Having all the components with a mysterious props object can be confusing.
How can you improve this?
Object destructing and rest operator comes to your aid.
// Variant One
const TopBar = ({ children, fluid = false, ...rest }) => {
/** Your Component Logic */
};
// Variant Two (Inspired from @reach/ui)
const Input = (props) => {
const {
as: Comp = "div",
"aria-labelledby": ariaLabelledBy,
"aria-label": ariaLabel,
children,
defaultValue,
disabled = false,
form,
name,
onChange,
required,
value: valueProp,
...rest
} = props;
/** Your Component Logic */
};
The best part?
Your component gains instant clarity and you save your code from an infinite repetition of the "props" word.
Furthermore, you can achieve another best practice related to the previous one. You can easily assign default values to your props.
Avoid Props Hell, Favor Composition Instead
We talked about this previously. Dealing with too many props is painful and weird.
Lack of clarity, difficulty in breaking down how the component works, prop drilling, even the choice of prop name becomes difficult.
The good news?
Most of the time you can come out on top.
Here's what you can do:
const Header = () => {
return (
<Navbar className="nav">
<Navbar.Logo src="/img/logo.png" />
<Navbar.Items className="items">
<Navbar.Item className="item" href="/">
Home
</Navbar.Item>
<Navbar.Item className="item" href="/guestbook">
Guestbook
</Navbar.Item>
</Navbar.Items>
</Navbar>
);
};
This is called the Compound Component Pattern. And as you can see it gives you a lot of advantages:
- Components are broken down by default.
- Prop drilling vanishes.
- Complexity is significantly reduced.
The dark side is that you need some initial boilerplate but it is definitely worth the case most of the time. Furthermore, you can optimize this boilerplate and save a lot of time, more on this later.
Best Practices Dealing With Clean Code
Clean code is a must for any self-respecting developer; here are some techniques you can apply right away.
Keep Utility Functions Outside of The Component
During component development, it's common to create helper functions to deal with repetitive problems. Here's an example.
const Title = ({ data = "" }) => {
const capitalize = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
const capitalized = capitalize(data);
return (
<Box as="h1" fontSize={24}>
{capitalized}
</Box>
);
};
So much useful, but there's a catch.
Keeping those functions inside components lets them be re-created every time the component re-renders. Not nice at all.
Bottom line? If those functions are not closures it's better to move them outside of the component.
Decouple Stateful Logic Using Hooks
Hooks are amazing and should be put to good use to achieve magnificent results.
Have a look at this harmless component, I omitted the extra code for simplicity.
const Steps = ({ steps = 1 }) => {
const { current, goToNext, goToPrev, canGoToNext, canGoToPrev } =
useSteps(steps);
return (
<>
<Button disabled={!canGoToPrev} onClick={goToPrev}>
Prev
</Button>
<Text>{current}</Text>
<Button disabled={!canGoToNext} onClick={goToNext}>
Next
</Button>
</>
);
};
Nice and easy? It is immediately clear what's happening.
And that's not all.
If for some reason you need to implement another component with this step logic, only a copy and paste separates you from doing it.
For the record, here is what the component would look like without hooks.
const Steps = ({ steps = 1 }) => {
const [current, setcurrent] = useState(1);
const canGoToNext = useMemo(() => current + 1 <= steps, [current, steps]);
const canGoToPrev = useMemo(() => current - 1 >= 1, [current]);
const goToNext = useCallback(() => {
if (canGoToNext) {
setCurrent((step) => step + 1);
}
}, [canGoToNext]);
const goToPrev = useCallback(() => {
if (canGoToPrev) {
setCurrent((step) => step - 1);
}
}, [canGoToPrev]);
return (
<>
<Button disabled={!canGoToPrev} onClick={goToPrev}>
Prev
</Button>
<Text>{current}</Text>
<Button disabled={!canGoToNext} onclick={goToNext}>
Next
</Button>
</>
);
};
Best Practices Dealing With Performance
Before starting with this section, here's for you the cornerstone of the advice:
Optimize Re-Renders
In case of really needing, you can optimize your renders using Memo, useMemo or useCallback.
You could even lift some components passing them as props to avoid a re-render for that component.
// It renders just one time!
const ListHeader = () => {
console.log("Header Rendered!!");
return <h1>I'm the Header</h1>;
};
const List = ({ header }) => {
console.log("List Rendered!!");
const [counter, setCounter] = React.useState(0);
return (
<div>
{header}
<p>I am the List</p>
<button onClick={() => setCounter((curr = curr + 1))}>
Increment: {counter}
</button>
</div>
);
};
export default function App() {
return (
<div>
<List header={<ListHeader />} />
</div>
);
}
Now I need to tell you a couple of things.
- Your component re-render twice? It's fine.
- A child component rendering triggered by the parent? Most of the time it's fine.
Please don't make the crucial error of polluting the codebase with "no-optimizing" render optimizations.
When is this optimization needed?
Remember the example of the steps hook we talked about before?
Here's one case.
Why? Because you are abstracting some logic that potentially a colleague of yours won't see or won't be able to touch (think of an external library for example).
In this way, you ensure proper behavior in case functions are exported from the hook is passed inside components wrapped in Memo.
Look at the Weight of your External Dependencies
Sad short story.
I once saw a case where to validate a simple JSON an "ultrafast" library was installed that weighed "only" 70kB Gzipped...
Now that's an improvement!
Although there are some useful tips for saving the size of your bundle, it is a good idea to use tools like BundlePhobia and if you find problems look for alternative packages.
Final Thoughts
I want to leave you with some simple advice which is a masterpiece in my opinion.
We are not robots, all the techniques described have been collected from various experiences across many projects, but there will always be the "edge case" which will invalidate a particular "best practice".
Using common sense you will unlock the full power of developers. Be creative, and find the right compromise between effort and result.
Hope you enjoyed it. Reach me on Twitter for a quick chat!