Search code examples
javascriptreactjstypescriptstyled-components

Styled-components React HTML elements factory


I want to create a component handling multiple HTML element with the same properties/logic.

import React from 'react';
import styled from 'styled-components';

interface GridProps {
  htmlElement: 'div' | 'main' | 'header',
}

const GridFactory = (props: GridProps) => {
  switch (props.htmlElement) {
    case 'header':
      return styled.header``;
    case 'main':
      return styled.main``;
    case 'div': default :
      return styled.div``;
  }
}

export const Test = () => (
  <GridFactory htmlElement='div'>
    <p>content...</p>
  </GridFactory>
)

It fails with that error :

Type '{ children: Element; htmlElement: "div"; }' is not assignable to type 'IntrinsicAttributes & GridProps'.
  Property 'children' does not exist on type 'IntrinsicAttributes & GridProps'.

First trick tried

Add an explicit children prop to GridProps :

interface GridProps {
  htmlElement: 'div' | 'main' | 'header',
  children?: React.ReactNode | React.ReactNode[];
}

It gives the corresponding error :

'GridFactory' cannot be used as a JSX component.
  Its return type 'StyledComponent<"header", DefaultTheme, {}, never> | StyledComponent<"div", DefaultTheme, {}, never>' is not a valid JSX element.
    Type 'StyledComponent<"header", DefaultTheme, {}, never>' is not assignable to type 'Element | null'.
      Type 'String & StyledComponentBase<"header", DefaultTheme, {}, never> & NonReactStatics<never, {}>' is missing the following properties from type 'Element': type, props, key

How can I achieve it ?


Solution

  • Since your styled.head returns component type rather than React.Element so you can improve your stuff as below:

    Specify returned type of your component as stateless functional component React.SFC, you also benefit from this by no need to specify children prop cause it's a part of the type SFC:

    const GridFactory: React.SFC<GridProps> = (props) => { ...
    

    Then assign styled component as functional component as well such as Header:

    const Header: React.SFC = styled.header``;
    

    To sum up, the full code would be:

    interface GridProps {
      htmlElement: 'div' | 'main' | 'header',
    }
    
    const Header: React.SFC = styled.header``;
    
    const GridFactory: React.SFC<GridProps> = (props) => {
      switch (props.htmlElement) {
        case 'header':
          return <Header />;
    
        // More to come
    
        default: return null
      }
    }