Back to posts

Using Composition to avoid defining wrapper components in React

June 3, 2025
2 min read

When using React, there come many cases when you need to write wrapper components or extract new components when suddenly you need to use some hooks in already written code or, in our example case, to use Suspense boundaries.

Imagine you have such component that renders a Select component, which awaits with use hook it's options from optionsPromise (let's imagine the promise came from a parent component):

1const SelectWithOptions = (optionsPromise: Promise<string[]>) => {
2  const options = use(optionsPromise);
3
4  return <Select options={options} />;
5};

One issue with this component is that this component would block the rendering of the page until the options request is completed. To solve this, we would need to wrap this component in a suspense boundary and ideally pass some fallback to be shown while the Suspense boundary content is suspended.

1const WrapperSelectWithOptions = ({ optionsPromise }: Promise<string[]>) => {
2  return (<Suspense fallback={<SelectSkeleton />}>
3    <SelectWithOptions optionsPromise={optionsPromise} />
4  </Suspense>)
5}
1const SelectWithOptions = (optionsPromise: Promise<string[]>) => {
2  const options = use(optionsPromise);
3  
4  return <Select options={options} />;
5};

As we can see, we needed to create a wrapper component just to be able to wrap the component that uses use to await the promise. This might not be too painful, but it can be annoying to need to create such wrapper components in each such case.

Solution

To avoid creating such wrapper components, we can define a helper component WithUse that would use the use hook and pass the "awaited" value as a prop to a children render function:

1const WithUse = <T,>({ children, value }: { children: (T) => ReactNode; value: Promise<T> }) => {
2  return children(use(value));
3};

and then use it like this:

1const SelectWithOptions = (optionsPromise: Promise<string[]>) => {
2  return (
3    <Suspense fallback={<SelectSkeleton />}>
4      <WithUse value={optionsPromise}>
5        {(options) => <Select options={options} />}
6      </WithUse>
7    </Suspense>
8  );
9};

Extra win we get is that now all the code for rendering of the SelectWithOptions component is colocated in one place: both the Suspense with the fallback skeleton component and the Select component itself with its options.

One thing to keep in mind when using this approach in Next.js with RSC: you can't use this component in a server component. The reason: children is no longer a ReactNode but a render function (() => ReactNode) and render functions cannot be serialized from server components to client components.