DEV Community

Cover image for ⚛️ Organizing Code in a React Component
Will T.
Will T.

Posted on

⚛️ Organizing Code in a React Component

Organizing code in a React component is often overlooked sometimes, but things can get complex when dealing with Higher-Order Components (HoCs), forwardRef, and memo. It can lead to cluttered and hard-to-maintain code if not handled properly. This article aims to address these issues by proposing a more manageable way to structure your code and improve your productivity.

1️⃣ Managing HoCs, forwardRef, and memo in a React Component

Problem Statement

As a React developer, I used to struggle often with organizing my components, especially when incorporating memo and forwardRef. The example below, taken from the React TypeScript Cheatsheet, demonstrates this challenge:

import { forwardRef } from 'react';

interface FancyButtonProps {
  type: 'submit' | 'button';
  children?: React.ReactNode;
}

export const FancyButton = forwardRef<HTMLButtonElement, FancyButtonProps>((props, ref) => (
  <button ref={ref} className="MyClassName" type={props.type}>
    {props.children}
  </button>
));
Enter fullscreen mode Exit fullscreen mode

From my own experience, this code presents several problems:

  • The code appears cluttered compared to a "bare" component due to the wrapping of the component within forwardRef.
  • Types are not really in the optimal places. The generics of forwardRef have a reversed order compared to the actual parameters used in the component: <HTMLButtonElement, FancyButtonProps> vs (props, ref).
  • Refactoring becomes cumbersome, as it requires careful attention to brackets and parentheses. Say you no longer need forwardRef. Even with the help of something like Rainbow Brackets, dealing with those brackets and parentheses can be frustrating sometimes, especially when the component is large.
  • What if you want to add memo or some kinds of custom HoCs to the component, and some states or functions inside it? It'll become even more of a mess:
import { forwardRef, memo } from 'react';

import { useStyles } from '@/modules/core/styles';

interface FancyButtonProps {
  type: 'submit' | 'button';
  children?: React.ReactNode;
}

export const FancyButton = memo(
  forwardRef<HTMLButtonElement, FancyButtonProps>(({ type, children }, ref) => {
    const classes = useStyles();

    return <button ref={ref} className={classes.button} type={props.type}>
      {children}
    </button>
  })
);
Enter fullscreen mode Exit fullscreen mode

Proposed Solution

After getting fed up with all the trouble when organizing my component code that way, I eventually came up with a way to tackle those issues. Consider the following approach:

import { forwardRef, memo } from 'react';

import { useStyles } from '@/modules/core/styles';

interface FancyButtonProps {
  type: 'submit' | 'button';
  children?: React.ReactNode;
}

const FancyButtonBase = (
  { type, children }: FancyButtonProps,
  ref: React.ForwardedRef<HTMLButtonElement>
) => {
  const classes = useStyles();

  return (
    <button ref={ref} className={classes.button} type={type}>
      {children}
    </button>
  );
};

export const FancyButton = memo(forwardRef(FancyButtonBase));
Enter fullscreen mode Exit fullscreen mode

The code is self-explanatory. The actual code of the component lives in a function component whose name has a Base suffix to it. Then we export the component below, with all the HoCs, memo, and forwardRef.

This solution offers several benefits:

  • The actual component code is separated from the HoCs, memo, and forwardRef, making it resemble a "bare" component.
  • Types are where they should be: (props: FancyButtonProps, ref: React.ForwardedRef<HTMLButtonElement>.
  • Should you need to refactor this code, as in you no longer use ref, you simply have to remove the second parameter and forwardRef without having to deal with brackets and parentheses.
  • If nested HoCs are required, adding or modifying them is straightforward and the code still remains readable:
import { forwardRef, memo } from 'react';

import { useStyles } from '@/modules/core/styles';
import { withWhatever } from '@/modules/core/hocs';

interface FancyButtonProps {
  type: 'submit' | 'button';
  children?: React.ReactNode;
}

const FancyButtonBase = (
  { type, children }: FancyButtonProps,
  ref: React.ForwardedRef<HTMLButtonElement>
) => {
  const classes = useStyles();

  return (
    <button ref={ref} className={classes.button} type={type}>
      {children}
    </button>
  );
};

export const FancyButton = memo(forwardRef(withWhatever(FancyButtonBase)));
Enter fullscreen mode Exit fullscreen mode

This approach adds a single line of code to your component but significantly improves readability and maintainability. Even without using any HoCs, memo, or forwardRef, I still do this for "bare" components: export const FancyButton = FancyButtonBase.

We can take this a step further and try to improve our productivity with a very useful but not well-known VSCode extension.

2️⃣ Improving Productivity with the "Folder Templates" Extension

The GIF below demonstrates the power of the "Folder Templates" extension in VSCode (the GIF might take a bit long to load):

Folder Templates Extension Demo

You only have to enter the name of the component, and voila!

Basically, you can also tell AI to write this boilerplate code for you, but I find using this extension much quicker, and it's highly customizable.

If you need me to share my pre-defined React-specific folder template settings, please let me know in the comments.

🏁 Conclusion

Organizing code in a React component, especially when using memo, forwardRef, and HoCs, can be a daunting task. However, by separating the actual component code from the HoCs and ensuring proper type declarations, you can create cleaner, more maintainable code. Additionally, using tools like the "Folder Templates" extension in VSCode can help streamline your development process and boost productivity.

In case you think it was a good read, you'll probably find my previous post useful as well:


If you're interested in Frontend Development and Web Development in general, follow me and check out my articles in the profile below.

Top comments (13)

Collapse
 
sirmay1 profile image
William Castro

I’m still learning React but I think I saw something within the docs which stated you no longer need to write forwardRef. Refs are consider to be apart of props. I noticed though if you use Vite for your React project this is the case but if you use create-react-app you still need to use forwardRef. Please correct me if I am wrong on any of these points

Collapse
 
itswillt profile image
Will T.

I haven’t heard of this at all. Would you mind pinpointing the exact website where this is mentioned? Or have a codesandbox demo for it. That’d be much appreciated. Thanks!

Collapse
 
sirmay1 profile image
William Castro

Hi Will, well it wasn’t an article but it was on YouTube under a YouTuber called Theo, I’m pretty sure you may have heard of him. Basically he created a video on new updates for React 19. On this video he talks about these changes and he even refers to one of the React core devs with a tweet from twitter.
The tweet from twitter read

“useMemo, useCallBack, memo -> React Compiler

forwardRef —> ref is a prop

React.lazy -> RSC, promise-as-child

use context -> use(Context)

throw promise -> use(promise)

-> ”

Posted by Andrew Clark

Maybe I misunderstood? If I misinterpreted the tweet and the video I am open to your point of view.
BUT when I excluded the forwardRef from my Vite project it worked. When I left it on my Vite project, errors appeared every where within my component.

Thread Thread
 
itswillt profile image
Will T.

Ah yes, I often watch him too. I've heard of it, but it's supposedly a change from React 19. I don't know if it works on React 18 in a Vite project. I'll give it a try and get back here.

Thread Thread
 
itswillt profile image
Will T.

I've verified this multiple times, and it doesn't seem to be true. I'm also using Vite. Perhaps you've installed some specific plugin for it?

Thread Thread
 
sirmay1 profile image
William Castro

I don’t know? The only plug in I added was react-router-dom?
I am still fairly new so it is possible it is just skill issues. But thank you for the reply and I will review again my previous attempts to see if I run into the same issues. Thanks.

Collapse
 
dwayne profile image
Dwayne Crooks

Thankfully in Elm we don't have to worry about any of that.

import Html as H
import Html.Attributes as HA

type alias Props msg =
    { buttonType : ButtonType
    , children : H.Html msg
    }

type ButtonType
    = Submit
     | Button

fancyButton : Props msg -> H.Html msg
fancyButton props =
    H.button
        [ HA.class "MyClassName"
        , HA.type_ <|
            case props.buttonType of
                Submit ->
                    "submit"

                Button ->
                    "button"
        ]
        [ props.children ]
Enter fullscreen mode Exit fullscreen mode

In Elm, we don't call it a component. It's just called a reusable view function.

Collapse
 
itswillt profile image
Will T.

That’s cool!

Collapse
 
tuanlc profile image
Lê Công Tuấn

Nice one! Keep it up 💪

Collapse
 
itswillt profile image
Will T.

Thanks!

Collapse
 
elsyng profile image
Ellis

Proposed solution: don't use HoC, fRef, memo.

Don't over-organise, don't overthink, don't over-optimise.

Simple is good.
😊

Collapse
 
itswillt profile image
Will T.

Proposed solution: Don’t use React.

Simple is good.
😊

JK. But honestly all these things are only inherent to React.

Collapse
 
elsyng profile image
Ellis • Edited

Typically, a web app which would be very complex and impossible to manage and maintain, becomes very simple, safe and easy to manage and maintain. And fun and pleasure to develop (DX).

Huge, huge difference. At least relatively speaking.

And secondly, you can implement React in a simplistic way, and you can implement the same app in a convoluted way. The same app. (People who end up with a complex React app don't understand really understand what React, component based programming and the KISS principle, imho.)

That's in my experience and what I know, at least ;o)