Reputation: 77
I am having two issues with using styled components in a hoc wrapper in react.
Anyone who can help with this?
interface IProps {
id: string;
left: number;
top: number;
}
export const Node: React.FC<IProps> = ({ id, left, top }) => {
return (
<Container left={left} top={top}>
{id}
</Container>
);
};
function withColors<T>(Component: React.ComponentType<T>) {
const bg = "hotpink";
const ComponentWithAddedColors = styled(Component)`
${bg && `background: ${bg};`}
`;
const result: React.FC<T> = (props) => (
<ComponentWithAddedColors {...props} />
);
return result;
}
const DraggableNode = withColors(Node);
export default DraggableNode;
I have made a code sandbox to illustrate the issue: https://codesandbox.io/s/styled-hoc-xgduo?file=/src/Components/Node/Node.tsx
Upvotes: 2
Views: 1520
Reputation: 42278
@Mosh Feu's comment pointed me in the right direction.
You can add styles to an already styled component and you can add styles to a custom component, but those two things work differently. You have a chain that goes through both types, so things are getting lost.
When you call withColors(Node)
what this is doing is passing a generated className
prop to Node
. But your custom component Node
never does anything with this prop, so the style is never applied.
The styled method works perfectly on all of your own or any third-party component, as long as they attach the passed className prop to a DOM element.
If we edit Node
to use this className
, we get the color!
export const Node: React.FC<IProps & {className?: string}> = ({ id, left, top, className}) => {
return (
<Container left={left} top={top} className={className}>
{id}
</Container>
);
};
As far as the typescript errors are concerned, you're getting an error about assigning your props T
to the props of a styled component (ComponentWithAddedColors
), which shows up as a bunch of crazy nonsense:
(props: (Pick<Pick<(PropsWithoutRef & RefAttributes<Component<T, any, any>>) | (PropsWithRef<PropsWithChildren> & {}), Exclude<...> | ... 1 more ... | Exclude<...>> & Partial<...>, Exclude<...> | ... 1 more ... | Exclude<...>> & { ...; } & { ...; }) | (Pick<...> & ... 2 more ... & { ...; })): ReactElement<...>
This is mainly because of ref forwarding through the ForwardRefExoticComponent
type.
But we can work backwards to get the expected props type from the component type using a utility type:
type PropsOf<T> = T extends React.ComponentType<infer P> ? P : never;
So ComponentWithAddedColors
has props PropsOf<typeof ComponentWithAddedColors>
. We could use that, but we also know that ComponentWithAddedColors
has type StyledComponent<React.ComponentType<T>, any, {}, never>
, so we can go back a step further:
type StyledProps<InitialProps> = PropsOf<StyledComponent<React.ComponentType<InitialProps>, any, {}, never>>
So ComponentWithAddedColors
has props StyledProps<T>
.
That said, all of this is unnecessary, at least in the example you've shown. You are passing all of the props of ComponentWithAddedColors
through to ComponentWithAddedColors
, so result
is the same thing as the component itself. Just return it directly.
function withColors<T>(Component: React.ComponentType<T>) {
const bg = "hotpink";
return styled(Component)`
${bg && `background: ${bg};`}
`;
}
Upvotes: 3