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