Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap every exported comopnent with HOC?

I need to add to ALL of my React function components the possibility to add [data-test-id] attribute for testing purposes. To achieve that I created withTestId() HOC which adds optional prop testId to wrapped component and when it's defined it adds [data-test-id] to final HTML.

So when I define component like:

<ExampleComponent testId="example" />

it returns:

<div data-test-id="example" />

The only problem I have is to apply it to every component without the necessity to wrap it individually in every component. So instead of writing code like:

function ExampleComponent() { ... }

export default withTestId(ExampleComponent)

I would like to wrap all of my exports in my index.ts file, which right now looks like this:

export { default as ExampleComponent } from "./ExampleComponent";
export { default as ExampleComponent2 } from "./ExampleComponent2";
...

How can I achieve this?

like image 240
Dawid Krajewski Avatar asked Dec 11 '25 19:12

Dawid Krajewski


1 Answers

I see two ways of doing this; One dynamic way, making the user-code of your library a bit more convoluted. with you being able to change the implementation easily and another one with a bit more boilerplate code, keeping the user-code as it is.

I haven't tested their behavior regarding tree-shaking when bundling the code.

Using destructing in user-code

This allows to add / remove things from your main component export file without having to worry about additional boilerplate in your library. The higher-order-component can be switched on/off easily. One caveat: The user code needs to use destructuring to retrieve the components.

Your new index.ts file would look like this, while I've called your previous index.ts file components.ts in the same directory:

import * as RegularComponents from "./components";
import withTestId from "./with-test-id";

const WithTestIdComponents = Object
  .keys(RegularComponents)
  .reduce((testIdComps, key) => {
    return {
      ...testIdComps,
      [key]: withTestId(RegularComponents[key])
    };
  }, {});

export default WithTestIdComponents;

To use it in your application code:

import MyComponents from "./components/tested";
const { Component1, Component2, Component3, Component4 } = MyComponents;

This uses the default export to make it look like you have all components in one place, but since you cannot destructure exports directly, you need this second step to get the correct components out of it.

Add boilerplate to the export file

Since there is an index.ts file with all the components exported in the library, one could import/rename each component and re-export them with withTestId and their name:

import withTestId from "./with-test-id";
import { default as TmpComponent1 } from "./component1";
import { default as TmpComponent2 } from "./component2";
import { default as TmpComponent3 } from "./component3";
import { default as TmpComponent4 } from "./component4";
export const Component1 = withTestId(TmpComponent1);
export const Component2 = withTestId(TmpComponent2);
export const Component3 = withTestId(TmpComponent3);
export const Component4 = withTestId(TmpComponent4);

This way, imports can be used as before:

import {
  Component1,
  Component2,
  Component3,
  Component4
} from "./components";

I'd argue that using index files already is some kind of boilerplate and this approach adds to it. Since the user code does not need any changes, I'd favor this approach.

In one of our projects, we have used a custom takeoff script to create this kind of boilerplate for us, whenever we generate a new component.

Examples

Here is a code sandbox to see both approaches.

like image 183
Narigo Avatar answered Dec 14 '25 09:12

Narigo