10 Anti-Patterns in React: Tips and Tricks for Better Code

Introduction

React.js is an elegant and minimal UI library that allows you to write JavaScript functions to represent components. It's a popular choice among web developers in 2022, but despite its simplicity, React can be complex and prone to anti-patterns. In this blog, we will explore 10 common anti-patterns in React and provide tips and tricks to improve your code. Whether you are a beginner or an experienced developer, these insights will help you write better React code and prepare for technical interviews.

1. Overly Large Components

When starting a React app, developers often write one big giant component, especially in the App component provided by create-react-app. While this approach allows you to quickly prototype and iterate, it can lead to an anti-pattern of having one overly large and deeply nested component. Large components are hard to understand, refactor, and test. To improve your code, consider refactoring the large component into smaller, reusable components. For example, a navbar component can be broken down into multiple components that better represent its functionality. To simplify the refactoring process, you can use VS Code extension like Glean, which automates the extraction of code into separate components.

2. Nesting Components

Another common anti-pattern in React is nesting components too deeply. While nesting components can feel intuitive and work perfectly fine, it can lead to performance issues and unpredictable behavior. Every time the parent component is rendered, the child component gets a new memory address. To avoid these issues, you can either not define a child component at all or move the child component out of the parent and pass the necessary function as a prop.

3. Inefficient State Updates

React makes it easy to manage state, but inefficient state updates can cause performance bottlenecks. For example, if you have a component with two different pieces of state, and an expensive calculation is required whenever one of the values changes, updating the state will trigger the expensive calculation every time, even if it only depends on one value. To optimize this, you can use the useMemo hook, which remembers the last value and only runs the calculation when the dependent data changes. React also provides the useCallback hook for working with functions.

4. Multiple Root Elements

React components can have only one root element. When defining a brand new component, you might encounter an error when trying to return two sibling elements together. To resolve this, you can wrap the elements with a div, but this leads to unnecessary divs in your markup, which can cause issues with accessibility and CSS styling. React provides a built-in Fragment component or an empty element syntax as a more elegant solution to render multiple elements without introducing unnecessary divs.

5. Organizing Components

As your React app grows in complexity, it's essential to have an opinionated way to organize your components. A good rule to follow is one component per file. While it can be tempting to export multiple components from a single file, this approach quickly leads to complexity. In larger projects, consider giving every component its own directory, with the main component file named index.js. This directory structure allows you to easily export the component from the directory and include other files like CSS modules or tests. Additionally, you can use a barrel file to export the component from a regular named file, providing concise imports and readable file names.

6. Slow Initial Page Load

Big and complex React apps can suffer from slow initial page load due to the time it takes for the browser to download the JavaScript bundle. To mitigate this issue, you can implement code splitting, which allows you to load modules asynchronously using dynamic imports. While dynamic imports work well with plain JavaScript, React components require additional support for lazy loading. React provides a lazy loading feature, still experimental as of today, which allows you to lazily load React components and show a fallback UI while the component is loading. This technique significantly improves the initial page load performance.

7. Prop Drilling

When you have a deeply nested component that needs to use state from a top-level component, prop drilling becomes a problem. Prop drilling requires passing the state as a prop through intermediate components that do not actually need it. This can lead to messy code and reduced maintainability. One solution to prop drilling is using a state management library like Redux. However, if you have global data that needs to be shared across multiple components, the Context API provides a lightweight alternative. The Context API allows you to provide data at a certain point in the component tree, and any nested component can access that data without passing it through intermediate components. However, be cautious when using the Context API, as it can make components difficult to reuse without the necessary provider component.

8. Prop Plowing

Prop plowing refers to the situation where a component has many different props, resulting in repetitive code. To simplify code passing, you can use the spread syntax to pass all props at once. This approach improves code conciseness, especially when dealing with components with a significant number of props. However, be aware that using the spread syntax makes the code less explicit, so it's recommended to use it judiciously.

9. Messy Event Handlers

Event handlers in JSX can become messy when you need to pass additional arguments alongside the event. Creating arrow functions for each event handler can clutter the code, especially when used in multiple places. A cleaner approach is to use a curried function, where the outer function handles custom arguments, and the inner function handles the event passed by default. This technique eliminates the need to define arrow functions for each event handler and results in cleaner and more readable code.

10. Putting Everything in a Single State Object

When working with the useState hook, it's tempting to put all your data in a single object to reduce the number of state updates. However, React 18 automatically batches state updates, so calling setState multiple times in the same function won't trigger multiple re-renders. Putting everything in a single state object may make your code difficult to extract into a custom hook. Instead, consider organizing your code into smart and dumb components, where smart components control data and dumb components render UI with props. When things get complex or you want to reuse code, extract the logic into custom hooks, which are JavaScript functions that handle the business logic while treating components as dumb components.

Conclusion

In this blog, we've explored 10 anti-patterns in React and provided tips and tricks to improve your code. React's simplicity can sometimes lead to complex code patterns, but with the right knowledge and techniques, you can write better React code and avoid common pitfalls. Remember to refactor large components into smaller, reusable components, avoid excessive nesting, and optimize state updates. Use fragments or empty elements for multiple root elements, follow best practices for organizing components, and implement code splitting for faster initial page loads. Address prop drilling and prop plowing issues, simplify event handlers, and consider extracting logic into custom hooks. By applying these tips and tricks, you can create cleaner, more maintainable React code. Happy coding!

Previous Post Next Post

Contact Form