Kim Frost Nielsen
Kim Frost Nielsen

Reputation: 77

Styled components in a hoc react component

I am having two issues with using styled components in a hoc wrapper in react.

  1. The component is rendered, but not with the background color.
  2. The ComponentWithAddedColors is not valid typescript. Don't know why.

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

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42278

Style Errors Explained

@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.

Style Errors Fixed

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

TS Errors Explained

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>.

TS Errors Fixed

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

Related Questions