arbisoft brand logo
arbisoft brand logo
Contact Us

Why DX is important and How to Improve Your React Components

Muhammad Abdullah's profile picture
Muhammad Abdullah QureshiPosted on
7-8 Min Read Time
https://d1foa0aaimjyw4.cloudfront.net/Why_DX_is_important_and_How_to_Improve_Your_React_Components_90b3afe7cc.png

Say, for instance, you received a bug fix for a critical feature. You open the file, scroll down, and keep scrolling. The component goes down to 1,500 lines—hooks intertwined with rendering logic, nested ternaries masquerading as “UI states,” and a maze of props drilled through five levels of child components. 

 

Your cursor blinks. Where do I even start?

 

This isn't a hypothetical scenario. It’s a reality many developers face when working on mature codebases. Components balloon into monolithic entities over time, often justified by the urgency of feature delivery or the fear of breaking existing workflows. But while these “dinosaur-sized” components might work for the end user, they silently erode something critical - Developer Experience (DX).

 

In this blog, I’ll share why DX is the unsung hero of sustainable software development and share practical strategies to refactor unwieldy React components into clean, maintainable pieces. 

 

So, here goes. 

 

Recently, I had the pleasure of working on a project with a large user base and a lot of technical features that required extra care in order to keep backwards compatibility. 

 

When I joined the team, my first few tasks were either some modifications to existing features or some bug fixes. If you’ve had the pleasure of working as a Software Engineer on a new code base, you know that this can be a nightmare. And to add a pinch of salt on top of that, this is how the majority of the files looked:
 

unnamed (1).png

 

At first glance, the image doesn’t look all that interesting, but what if I told you that this is the last line of the file? And such numbers are a common occurrence in the project I’m talking about?

 

What’s even more interesting is that these files are not a utility or helper functions collection but rather React Components. Yes! A thousand-line React Component. Such components are very easy to create, or should I say, orchestrate, but for a new joiner, it's a nightmare to work on such components. It's very difficult for me to call these ‘components - components’ in the first place because a component is a reusable piece of the element. Can it be ‘reusable’ when it surpasses 4-digit line numbers and still be called a component?

 

Problems with Dinosaur-Bite-Sized Components

It works, and no one is questioning that the app works. Everything works, and the new person modifying is just pushing buggy code. The problem is not the person or the lack of testing or the lack of product knowledge, but rather the inability of the components themselves to accept a change. 

 

Whatever the size of a function or a class, each line of code depends upon other lines, and each line gets affected by changes in other lines. The larger the number of such lines, the greater the risk of introducing a bug. 

 

Not to mention, the app’s performance and the developer suffer equally. Eventually, people start using terms like tech-debt and refactoring, and spaghetti. The following points are just a few problems that occur when we have a large number of lines for a component:

 

  • Debugging becomes a nightmare.
  • The reusability of the code suffers.
  • Adding a change in the component gets difficult.
  • Making sense of the code becomes difficult
  • Easily avoidable re-renders become inevitable.
  • People don’t want to touch the file to avoid accidentally breaking something that was working just fine.

 

Do Small Components Solve Every Problem?

Well, they try their best. 

 

Smaller components are fairly easier to understand and work with. They are easier to test, too. The dependency of such components is on far fewer lines of code. It's easy to comprehend the working of those components. The biggest problems that small components solve are:

 

  • Improved Developer Experience.
  • Reduced clutter, cleaner, leaner code.
  • Improved render performance.
  • Greater reusability.

 

I Have a 1000-Line Component, Now What?

It’s not easy to improve the code, especially the code that was written a while back. Code doesn’t age like a fine wine. The older it is, the uglier it gets. We can, however, take a few steps to refactor such a component. The easiest way to begin will almost always be “Divide and Conquer."

Divide and Conquer

1. Find

Start with the innermost child of the component. It can be a button, a group of inputs, a dropdown, or an entire form element.

2. Separate

Take it out into a separate file and pass everything this part was using as props. It can be event handlers, classnames, etc.

3. Track

The props that you just passed to this brand-new component are its dependencies. Figure out what the dependencies of these newer dependencies are. If the dependencies originated within the parent component, there is a fair chance that you can just remove such dependencies from the props and let the child component handle them.

 

For instance, consider you were passing a submit handler to a form, and it was associated with some loading and error state. Now that the form has been separated out, the parent component doesn’t need to worry about the loading and error states. The form component is fully capable of handling these, even the submit handler sometimes.

 

A Practical Example

Keeping the divide and conquer steps in mind, I separated a part of the component into a much leaner component.
 

Keeping the divide and conquer steps in mind, I separated a part of the component into a much leaner component.

const statusQuery = useQuery({ ... });
const statusMutation = useMutation({ ... });
const fetchDetailsQuery = useQuery({ ... });

useEffect(() => {
    // some logic to alter the view
}, [statusQuery.data])

return statusMutation.isPending ? <Loader /> : <div>
    ...
    {statusQuery.isLoading ? null :
        <select onChange={(e) => statusMutation.mutate(e.target.value)}>
            {statusQuery.data.map(status => <option key={status}>{status}</option>)}
        </select>
    }
    ...
</div>;

 

The code above was handling fetching the data and mutating it. On careful inspection, we could see that some mutation parts can be removed as smoothly as a hot knife through butter. We separate out the Select into its own component. With this seemingly obvious change, we reduce the clutter in the parent component and make it render fewer times. In fact, there is only one Query call left in the parent component.
 

MVVM

Many people will be familiar with the MVC design pattern. It has a lot of variants, especially when it comes to React. One such variant is MVVM, where we replace our traditional controller with a view-model. The idea is simple - consider a component with ‘?.’ to avoid undefined data. 

 

For example:

data?.user && <h1>{data.user.name}</h1>

 

Such components are harder to manage and maintain. Again, primarily because the same component handles data fetching in either the useEffect and useState combination or using React-Query or similar tools. We can do better by defining an invariant for the component, a simple invariant: “The data will be there when this component renders.” This simple invariant reduces the clutter and simplifies the component logic.

<h1>{user.name}</h1>

 

And we create a view-model to handle the fetching of the data:

const query = useQuery({ ... });

return query.data ? <User user={query.data} /> : <Loader/>;

 

This doesn’t just end here, we can do even better by separating the fetching logic into its own encapsulation and creating something like the following:

const query = useUserQuery();

 

Adding a bit more complexity to the component will take us to an implementation like the following:

const userQuery = useUserQuery();
const updateUser = useUserMutation();
const updatePermissions = usePermissionsMutation();
…

 

Despite adding more features, we keep the components clean, small, and lean. Each functionality is encapsulated and doesn’t clutter the UI components, which leads to better DX, and things work much more efficiently.

 

In The End

Refactoring a 1,000-line component might feel complex, but remember - every journey begins with a single step. Start by carving out small, reusable pieces. Embrace patterns like MVVM to separate concerns. Celebrate incremental wins—a component reduced by 100 lines, a complex effect simplified, or a child component that no longer depends on parental chaos.

 

The benefits compound quickly. Debugging becomes faster. Performance improves as unnecessary re-renders vanish. Most importantly, developers regain their sanity—no more tip-toeing around fragile code or dreading the next “quick fix.”

 

Good DX is a necessity. It’s the difference between a codebase that thrives and one that slowly strangles productivity. So the next time you encounter a monolithic component, ask yourself - Can I make this a little better today?

 

Your future self—and your team—will thank you.

...Loading

Explore More

Have Questions? Let's Talk.

We have got the answers to your questions.

Newsletter

Join us to stay connected with the global trends and technologies