DEV Community

Cover image for Decorating with React and TypeScript
Alexander Glassford
Alexander Glassford

Posted on

Decorating with React and TypeScript

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:

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

Enter fullscreen mode Exit fullscreen mode

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

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()
  );
};

Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  <Decorator {...(decoratorProps as T)}>{renderStuff()}</Decorator>
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

And here 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!

Top comments (0)