Decorating with React and TypeScript
Tags: react, typescript, designpatterns, webdev
Published: 2024-05-31 16:38:20

Hello!

Have you been searching article after article, post after post, looking for a way to pass a React component as a prop to another component? 

Me neither.

In fact, I think I've read all of the articles regarding that topic that are available on the internet... but that's not what I've been searching for.

And that's what keeps coming up when I search for things like:

- "React component composition"
- "How to pass an un-rendered component as a prop so I can render it later"
- "How to apply props to a component passed in as a prop"
- "Pass a component as props and pass its props as props and render that component inside another component with those props"

I should add that this is all to be done in TypeScript.

Here's my description of what I wanted to do:

_Create a react component that is able to accept another component as a prop, and then render that component around it's own contents._

The reason that I want to do this is so that I can wrap a container component (A) around another component (B) and allow A to be able to access state and props that exist within B.

From what I understand, this is similar to the **Decorator Pattern**... or it just _is_ a **Decorator Pattern**...

Make sense? 

Let's look:

```tsx
const Decorator = ({
  title,
  footer,
  children,
}: PropsWithChildren<{ title: string; footer: string }>) => {
  return (
    <div>
      <h1>{title}</h1>
      <div>{children}</div>
      <span>{footer}</span>
    </div>
  );
};

```

Above we have a very simple component that we'll name `Decorator`. This is the component that I will want to use to wrap or _decorate_ another component

```tsx
const Decorated = <T,>(props: {
  Decorator?: ComponentType<T>;
  decoratorProps?: T;
}) => {
  const { Decorator, decoratorProps } = props;

  const renderStuff = () => (
    <div>
      <h1>Decorated</h1>
    </div>
  );

  return Decorator ? (
    <Decorator {...(decoratorProps)}>{renderStuff()}</Decorator>
  ) : (
    renderStuff()
  );
};

```

The `Decorated` component will take in two props: `Decorator`, a component, and `decoratorProps` which will be the props of the `Decorator` component.

If you're in your IDE with the TS compiler running, you should see that there is an issue with the instantiation of `Decorator`:
```
Type '{ children: Element; }' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{ children: Element; }'.typescript(2322)
```

I am not a TypeScript wizard, but it looks to me like `Decorator` is mad that it's receiving children or something. I'm not going to run you through all the attempts I made before getting to a viable solution that doesn't just ignore this rule, but know that there were many. What I finally landed on was this:
```tsx
  <Decorator {...(decoratorProps as T)}>{renderStuff()}</Decorator>
```

Typecasting `decoratorProps` to `T` solves all my problems... alright, not all of them, but enough of them for me to move past this task.

Here's an example of the implementation:

```tsx
const Decorated = <T,>(props: {
  Decorator?: ComponentType<T>;
  decoratorProps?: T;
}) => {
  const { Decorator, decoratorProps } = props;

  const renderStuff = () => (
    <div>
      <h1>Decorated</h1>
    </div>
  );

  return Decorator ? (
    <Decorator {...(decoratorProps as T)}>{renderStuff()}</Decorator>
  ) : (
    renderStuff()
  );
};


const DecoratorOne = ({
  title,
  footer,
  children,
}: PropsWithChildren<{ title: string; footer: string }>) => {
  return (
    <div>
      <h1>{title}</h1>
      <div>{children}</div>
      <span>{footer}</span>
    </div>
  );
};

const DecoratorTwo = ({
  title,
  footer,
  children,
  article,
}: PropsWithChildren<{ title: string; footer: string; article: string }>) => {
  return (
    <div>
      <h1>{title}</h1>
      <div>{children}</div>
      <div>{article}</div>
      <span>{footer}</span>
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Decorated />
      <Wrapper
        Decorator={DecoratorOne}
        decoratorProps={{
          title: "DECORATOR ONE",
          footer: "ONE FOOT",
        }}
      />
      <Wrapper
        Decorator={DecoratorTwo}
        decoratorProps={{
          title: "DECORATOR TWO",
          footer: "TWO FOOT",
          article: "this is an article",
        }}
      />
    </div>
  );
}

```

And [here](https://codesandbox.io/p/sandbox/decorator-m2ts5p?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clwuuyep800062v6ej5083sqy%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clwuuyep700022v6ebl6eiv6s%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clwuuyep800032v6enwfcgsmw%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clwuuyep800052v6eldxazdtf%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B57.16005189242297%252C42.83994810757703%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clwuuyep700022v6ebl6eiv6s%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clwuuyep600012v6ehot3dsdc%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Findex.tsx%2522%257D%255D%252C%2522id%2522%253A%2522clwuuyep700022v6ebl6eiv6s%2522%252C%2522activeTabId%2522%253A%2522clwuuyep600012v6ehot3dsdc%2522%257D%252C%2522clwuuyep800052v6eldxazdtf%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clwuuyep800042v6elg5hs9zu%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clwuuyep800052v6eldxazdtf%2522%252C%2522activeTabId%2522%253A%2522clwuuyep800042v6elg5hs9zu%2522%257D%252C%2522clwuuyep800032v6enwfcgsmw%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clwuuyep800032v6enwfcgsmw%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D) is a link to a sandbox where you can play around!

I know I didn't go into why what's happening is happening, but I hope that if you, the reader, have input or knowledge to share, you will! In the meantime, here's a way to implement a Decorator Pattern with React and Typescript!