STYLED-COMPONENTS IN TYPESCRIPT IMPLEMENTING THE DOT NOTATION
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 ofFlex
, 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.