10 min. read

August 09, 2022

9 Bad React Habits to Kick From Your Life

Here are some of the worst practices I’ve seen (and may or may not have done in the past 😅). Hopefully this will help you avoid making these same mistakes in the future.

Patrick Zawadzki

Patrick Zawadzki, Senior Software Engineer

React has become a word that developers worldwide hear on a regular basis. Since its creation, it has become incredibly popular. One of the best parts of React is that it is not incredibly opinionated on how to use it, which can make it quite powerful and easy to learn. However, too much flexibility also makes it easy to pick up bad habits and poor practices.

Practices and habits can vary from dev to dev, team to team, and company to company - that means, when you go to a new environment, you might be faced with a completely new way of doing things. Being aware of all the ways to approach coding in React can ultimately help any developer find the optimal solution to one. As developers and engineers, our goal should always be to build the best thing we can build within the constraints we have - all while using good practices.

Here are some of the worst practices I’ve seen (and may or may not have done in the past 😅). Hopefully, this will help you avoid making these same mistakes in the future.

1. Prop Drilling

Prop drilling is the practice of passing a single prop down from a parent, multiple layers deep to a child. 

props examples-min

Ideally, when passing a prop down from a parent to a child you should avoid going more than 2 layers deep. (Parent -> child (layer 1) -> child (layer 2)). You CAN, in fact, go as many layers as you choose, but be aware that prop drilling commonly causes some performance problems and a lot of headache for React codebases.

Namely, prop drilling can cause unnecessary re-rendering. As a component in React will always render when a prop is changed, intermediary components that are simply passing a prop down the chain will be forced to render when passing the prop down the chain. This can cause performance problems for an application in the long term.

Additionally, it's not fun to track down how data is being passed in an application. Having props pass down multiple layers deep makes finding how data is being used more of a challenge for someone trying to understand an application.

There are plenty of ways to handle this issue. You can try using the react context hook, restructuring your components, or using something like Redux. Unfortunately, Redux tends to be overkill and requires a fair amount of organisation and overhead for simpler applications. However, in scenarios where you need to change multiple things based on a single state change or have one state be the output of another, Redux may be best. 

2. Importing more than you need

React is, after all, a frontend framework. It is a library that creates your UI for your user and will be shipped to every browser/mobile device when it is being requested. A React application should be as sleek and slim as possible to avoid sending more than what it is needed to a user. Filling an application with extra dependencies and bloat will decrease performance and increase load times for a user, making your application seem slow.

less imports-min

In order for a user to have a good experience with your application, your First Contentful Paint should be fast (ideally between 0 - 1.8 seconds) so keep your shipped code down in order to simplify your bundle. Most modern app bundlers like Parcel and webpack do a lot of work to minify and compress your code for production, but it's good practice to be aware of what code is being shipped to your client and not just rely on app bundlers to attempt to fix things for you.

For example, when lodash was created it provided a lot of functionality that JavaScript didn’t provide. Nowadays modern JavaScript has gone through major updates and lodash no longer has that edge. Check out You Might Not Need Lodash to see if what you’re trying to do even requires lodash anymore. Granted, this also depends on what version of JavaScript your users would be using but most React applications will have Babel or some other transpiler in place to take care of this for you…plus everyone only ever supports chrome these days…right? 

IE meme-min

3. Not separating business logic from component logic

Generally speaking, it's best to try to keep your components as “UI related” as possible. These components should contain simple code and logic to display what they need to display and manage any state they need to display. While a component may need to make an API call on the first render to retrieve the information it needs to display, it's best to abstract away the logic behind the API call into a separate service utility file or something similar.

Separating out business logic from component logic allows for two things: the separation of concerns and the opportunity to reuse the business logic in other places. A UI component can become complex enough on its own through the logic required to display different parts of the UI. Adding business logic to this can only complicate the component more and make it more of a challenge to understand what is going on.

4. Duplicated work on each render

Rendering can happen often and unexpectedly. This is one of the core tenets of writing resilient components and should be kept in the back of your mind when writing React components. This also unfortunately means that any work that is done when a component is rendering will be re-executed every render. This is where the useMemo and useCallback hooks from React can come in handy. When used properly these hooks can help increase performance by memoizing certain operations that do not need to be repeated every render.

useMemo-min

In this example, our component is filtering a list of items based on a prop and displaying them to the user. This type of filtering isn’t always avoidable on the frontend, so this is a great example of when you can use useMemo on a list to prevent it from refiltering every render and only filtering when the items or the filter itself changes.

5. Using the useEffect hook improperly

The useEffect hook is probably one of the first hooks a React developer learns. In the class component days of React, componentDidMount was a common lifecycle function to use to assign event listeners, and in the world of hooks useEffect has taken its place. However, using the useEffect hook improperly means you will end up creating multiple event listeners.

useEffect-min

Unless you have linting of some sort enabled with the proper rules, removing the function return here is still valid React usage. Additionally, it's important to use the empty dependency array as the second parameter for the useEffect hook in order to ensure that the useEffect hook will only be run once. It's all too common to see the return function missing in a useEffect hook setting up event listeners and can lead to some tricky bugs to decipher.

6. Incorrect usage of Boolean operators

Most components have multiple Booleans in place to render/not render certain HTML elements on the page. This is totally normal. However, there are a few different ways to handle this logic in a component. The most commonly used one in a component is the ‘&&’ operator. While this is totally functional JavaScript, it can have some unintended consequences in your UI. For example:

boolean practice-min

In this instance, we want to have a component show us how many items we have in a shopping cart, but in this scenario you will end up just rendering ‘0’ on the page. When using a conditional render syntax like this, it's best to use Boolean instead of relying on JavaScript’s truthy/falsy comparison because, while it works, it can cause unintended side effects in React.

boolean good-min

Now, there are many different approaches to this, and ultimately it can come down to personal preference. Sometimes it will make more sense to break out a conditionally rendered component into its own component, and just return null if the condition isn’t met. Generally, I would suggest that trying to convert numbers/arrays/strings etc. into Booleans for the condition you are checking is the most practical way of conditionally rendering something.

7. Using ternary operators all over the place to conditionally render

A good old ternary expression can be incredibly satisfying when used correctly. But it can be all too tempting to use it to render between choices within a React component. The issue with these expressions is that they can end up wrapping large components and can be challenging to decipher when reading the code.

ternary-min

Ternary Example

There is nothing functionally wrong here, but even with three lines of code inside each portion of the ternary expression it looks congested. This can be overwhelming to read as the code expands and even worse as ternarys or other conditional rendering options become nested inside of these. Consider some of these other options as alternatives to this ternary expression.

option1-no ternary-min

Option 1: Use a function to abstract your rendering logic

option2-no ternary-min

Option 2: Have the components determine how they should render

Option 1 is a favourite because it lets you do more complex rendering logic in a more familiar way in JavaScript. Option 2 also works, but in a normal scenario, the two components being rendered conditionally would likely be in separate files and it may be more difficult to keep all of the rendering logic in sync if things need to change down the line. Option 1 requires only changes in one place to change your rendering logic. Option 2 also has some less than straightforward logic to follow in comparison to the helper function we’ve created.

8. Not defining prop types or not deconstructing props

No different to most things in JavaScript, React uses objects to store the values of props for components. This means that many different values can be passed within the object and not have them be easily known to the component. This can make it challenging to work on the component in question.

props examples-min

There are many different ways to approach this. In a JavaScript environment using the BestExample above will provide two different benefits. First of all, you will know right where your component is defined and what props are available to the component. This is beneficial because somewhere down the line if you want to change something, you won’t need to go digging to see what props are available.

Additionally, using propTypes on the component gives you more insight into what the component will expect to function properly. In your web browser’s console you will also receive warnings when certain conditions aren’t met on the component’s props. Say in this instance title or content is passed in as undefined or a number instead of the expected string. 

9. Not Using Code Splitting for larger applications

Large applications means a large UI bundle. With all the custom components and libraries being used, it can be quite large. Code splitting allows you to essentially ‘split’ your bundle into pieces that can be loaded and requested later, allowing your initial bundle to be smaller and the first piece of code needed to run your app be available faster to your userbase.

react-loadable-min

Code from react-loadable docs Some good places to consider code splitting is mobile/desktop UIs, routes that a user may not go to initially and modals. There are tons of different use cases here for this, but ultimately it's up to the developers and product teams to understand how users interact with the application and where it would make the most sense to prioritize the app being loaded. React has some examples for code splitting that you can use. On top of that you can also look into React-loadable as an NPM package that works quite well. React-loadable also has some cool patterns that fire a request on hover of an element to fetch required assets. This works quite well on a button that might open a modal that is being split.

In summary

React provides us a powerful toolset to create UIs and experiences for our users more seamlessly than before. Unfortunately, it is a set of tools and tools can always be misused. Using tools as they are intended, and the features of JavaScript in a way that is preferred, allows us to create more legible and functional code. Working on improving our output as developers, and making it easy for other developers to interact with our code, is a good goal to strive towards. Ultimately this will make our work more legible and hopefully reduce bugs in the process. I’ll leave you with one quote I heard that has changed how I view the code I write. It’s a bit gruesome but it gets the point across well. 

‘Write your code today like the next person who will need to read your code is a violent psychopath who knows where you live’. 

Looking to find new roles that match your ambitions? Honeypot is Europe's job platform for developers and data specialists. Sign up today and get offers with salary and tech stack up front. (P.S. Honeypot is always free for developers.)