When developing a library of React components, there’s often a scenario where a subcomponent is always supposed to be used with its parent. For example, there’s Flex and its child FlexItem. Or better yet, Flex.Item. The dot notation has a number of advantages, and it would be good to be able to implement it in TypeScript in combination with Styled-components.

A more complete example:

import { Flex } from 'Components.tsx';

<Flex fluid>
  <Flex.Item width={2}>
    <Avatar/>
  </Flex.Item>
  <Flex.Item width={1}>
    <UserInfo/>
  </Flex.Item>
</Flex>

The advantages of using the dot notation are these:

  • Namespacing: While the dot notation does not stop your using Flex.Item outside of Flex, it does give you a hint as to where it belongs.
  • Single imports: You only need to import the top-level component, rather than all child components individually. Child components can be added without changing the import statement.
  • Discoverability: Since the child components are actually static fields in the parent component, you can easily discover them with syntax completion in your favorite editor.

Implementation without Styled-components

In TypeScript, the dot notation is implemented as follows:

class Flex extends React.Component<IFlexProps> {
  public static Item = FlexItem;
  public render() {
    return ...;
  }
}

Implementation with Styled-components

With Styled-components thrown into the mix, this gets tricky. If you just wrap your Flex components with styled, then you lose access to your static field, as Styled-components doesn’t pass it on:

class FlexBase extends React.Component<IFlexProps> {
  public static Item = FlexItem;
  public render() {
    return ...;
  }
}

class Flex = styled(FlexBase)`
  ...
`

Flex.Item // Type error

You might be able to fiddle with the interface of your parent class, as suggested here, but since the internal implementation of Styled-components changes regularly, it might break in time even if you can get it to work.

Wrap the wrapper

Here is another trick:

class FlexBase extends React.Component<IFlexProps> {
  public render() {
    return ...;
  }
}

class FlexStyled = styled(FlexBase)`
  ...
`

class Flex extends React.Component<IFlexProps> {
  public static Item = FlexItem;
  public render() {
    let p = this.props;
    return (<FlexStyled {...p}></FlexStyled>);
  }
}

Since the Flex component doesn’t do anything but pass its properties to its child, it merely defers to FlexStyled and won’t put anything in the DOM.