@MaximeHeckel

How to efficiently type your styled-components with Flow

October 9, 2018 / 7 min read

Last Updated: October 9, 2018

This post is part of an informal series of small articles focusing on Flow types usages that I’ve discovered and found useful over the past few months.

For the past year, I’ve been using Flow as my static type checker on many of my projects whether they were personal or work-related. In the beginning, I was mainly using it as a replacement to React propTypes as they were difficult to enforce during dev and testing phases which resulted in misusages of some components way too many times.
However, while refactoring an entire codebase by adding proper Flow types, I realized that loosely typed components or functions were both harder to use and caused unwanted changes. That’s why I tried to look a bit more at how I could type them better. One of the main areas that I decided to focus on first was improving the static typing of some styled components. By adding Flow on top of these components, we can tighten their definition thus avoiding misusages before they hit production, and make developers’ and designers’ life easier.

In this short post, we’ll consider a simple example of a styled component with specific design specs and see how Flow can help to enforce these specs when using it.

What I needed to build

I was building a simple theme with a series of colors using emotion as well as a Title styled component that would have a color prop. The color will be injected through a theme prop. This prop comes either from a ThemeProvider component that wraps your app or from a withTheme Higher-order Component. I’m not going to detail more about the setup of emotion in this post but you can find all the adequate documentation here.

Here’s the component that we’ll use as an example:

The code of the Title styled-component

1
import styled from 'react-emotion';
2
3
const Title = styled('h1')(`
4
color: ${(props) => props.theme.colors[prop.color]}
5
`);
6
7
export default Title;

The aim here was the following: to make sure that anyone who uses the Title component could change its color via a prop but only let them pick the blue colors provided by the theme. Code-wise this is what we want to have:

Title component good and bad usage

1
// Good
2
<Title color="blue1">
3
Styled Components are awesome!
4
</Title>
5
6
// Bad
7
<Title color="red2">
8
Styled Components are awesome!
9
</Title>

This is one case when I discovered that Flow can surprisingly help to solve this kind of problem. The theme used for this example looks like the following:

Theme used for this example

1
// @flow
2
type Blues = {
3
blue1: '#099CEC',
4
blue2: '#6BC3F3',
5
};
6
7
type Reds = {
8
red1: '#E12B0C',
9
red2: '#FB786A',
10
};
11
12
type Greens = {
13
...
14
};
15
16
type Theme = {
17
colors: {
18
[string]: '#099CEC' | '#6BC3F3' | '#E12B0C' | '#FB786A' | ...
19
},
20
...
21
}
22
23
const blues: Blues = {
24
blue1: '#099CEC',
25
blue2: '#6BC3F3',
26
}
27
28
const reds: Reds = {
29
red1: '#E12B0C',
30
red2: '#FB786A',
31
};
32
33
const greens: Greens = {
34
...
35
}
36
37
const theme: Theme = {
38
colors: {
39
...blues,
40
...reds,
41
...greens,
42
},
43
... rest // rest of the properties of our theme
44
}

When it comes to color usage, we don’t want to leave the possibility for other developers to use a color outside of the theme. This is why creating a Theme type and the different color types like Blues and Reds (as well as the other properties of your theme) from the start is a good idea so you immediately document the do’s and don’t’s of your theme at the static typing checking level. In the rest of this post we’ll essentially focus on how to leverage these types like Blues to validate props of Title.

In the example above, we can see how enums can be useful: colors is a map of some string value (the name of the color) to one and only one of these 4 colors.

How to type a styled component

Typing the styled component is the first step**.** I didn’t know how to this at first so I had to do a bit of research on this one and ended up finding this comment on a Github issue which was very helpful. Following the example given in this issue, I wrote this typed implementation of Title:

First typed implementation of the Title component

1
// @flow
2
import type { ComponentType } from 'react';
3
import styled from 'react-emotion';
4
5
type TitleProps = {
6
color: string,
7
};
8
9
const Title: ComponentType<TitleProps> = styled('h1')(`
10
color: ${(props) => props.theme.colors[props.color]}
11
`);
12
13
export default Title;

It’s typed a bit fast but, it’s still better than having no type. Now we can use Title with a color prop as we wanted, but sadly here we can pass any string, i.e. any colors which doesn’t quite help us given what we want to build.

Enums

The next step was to type the component better, meaning, to make type it in such a way that it would only accept a subset of colors. The string type is way too generic. All the following examples would pass Flow without any error:

Example of valid but wrong usages of the typed Title component

1
<Title color="hello"/> // No error despite hello not being a color
2
3
<Title color="red1"/> // No error but we don't want Titles to be red

This is where enums comes into the picture. Indeed, by specifying the subset of the colors of the theme we want for Title we can narrow down which props can be passed to the component.

Updated TitleProps type

1
type TitleProps = {
2
color: 'blue1' | 'blue2',
3
};

This would mean that Flow would fail if we use Title with red1 as color:

Flow error showing that using 'red1' as color for the Title component is not valid

The $Keys utility type

However, whenever the theme gets updated with some extra blue colors, the type will have to be manually updated and we’ll have to add every single color name we want to be usable with Title for Flow not to fail in the future when these will be passed as props. This is ok if we have a limited number of possibilities, but with scalability in mind, it’s not very practical.

We can type our component even better using the $Keys utility type. Here’s how to use it:

Final implementation of the Title component using the $Keys utility function

1
// @flow
2
import type { ComponentType } from 'react';
3
import styled from 'react-emotion';
4
import type { Blues } from './theme';
5
6
type ValidColors = $Keys<Blues>;
7
8
type TitleProps = {
9
color: ValidColors,
10
};
11
12
const Title: ComponentType<TitleProps> = styled('h1')(`
13
color: ${(props) => props.theme.colors[props.color]}
14
`);
15
16
export default Title;

Here’s how$Keys works in our case: it extracted the type ‘blue1' | ‘blue2' from our Blues type by getting its keys. Thus, every time we update our color palette and the respective color types, our Title component will be properly typed. We can thus see that typing this way is more elegant than manually adding items to an enum.

In conclusion, we can see that Flow can be used for more than just typing components for the sake of typing, it can be leveraged to properly define our design-system in our apps which can be game-changing for our development flow. By clearly reflecting the specs requested for this problem as Flow types, we avoid any “bad surprises” in production as any unwanted changes can be now prevented during the testing phase. 
This is why, whether the project is big or small, I plan on using Flow even more when working on design-systems and themes in the near future.

Liked this article? Share it with a friend on Bluesky or Twitter or support me to take on more ambitious projects to write about. Have a question, feedback or simply wish to contact me privately? Shoot me a DM and I'll do my best to get back to you.

Have a wonderful day.

– Maxime

Adding Flow on top of styled components to avoid misusages before they hit production, and make developers’ and designers’ life easier.