Adopting styled components with Typescript in React Native

Mike Post
Bungalow Garage
Published in
6 min readMay 10, 2019

--

In the past year I’ve gained exposure to React Native, before briefly being exposed to React for the web. As a mobile developer, I didn’t stay in the React web world for too long, but one of the lasting impressions on me was the use of styled-components vs React Native’s more ubiquitous StyleSheet library.

Recently my mobile lead at Bungalow and I have been integrating it into the codebase, while trying to figure out an optimal way to use it in Typescript. We were previously already using StyleSheet, so we’re taking an iterative approach to it — mostly on new features.

Most of the examples we’ve found along the way have been in Javascript. So we thought it would be a good idea to share some knowledge about how we use styled components in Typescript. There are a few gotchas or alternative ways you can implement it — like in most languages, and Typescript is no different. So I’ll try to address some of those along the way.

The starting point

First up, the official styled-components docs describes how to integrate it, so we’ll skip that part. This will focus less on how to set up and use styled-components, and more of how we use it at Bungalow, and how to use it in Typescript.

To give credit where it’s due, they touch on Typescript a little bit. But we use a growing amount of functional components, and there’s not much in their docs apart from one simple example. So I’m going to continue on their good work and expand on that example a little bit.

The example they give is one of a Box component, which has the following attributes:

First of all, let’s do 2 things:

  1. Simplify that declaration.
  2. Replace that div tag with something more aligned to React Native.

If this looks like more code, it’s because I’ve included the import statements and exported the component to help make this more of a complete example, as if this belonged in a real file somewhere in your directory named Box.tsx. We’re not using the hypothetical theme they have for padding and colors yet, ThemeInterface, but we’ll get back to that below.

We’ve removed the attribute for className because we don’t need that anymore.

  • We don’t need that because we’re declaring our styled components to be composed within our main component, rather than the opposite approach in the original example of wrapping around the container component.
  • This way we can use many styled components in our exportable component, and we also want to continue to use styled with the same advantages that StyleSheet offers — to abstract all of the styling out of our JSX.

In short:

  • styled components = styles
  • main component = JSX

What about retaining className for testing? Because we’re taking care of the styles internally to the module, I assume the original example was using className for automated tests or analytics. Removing className still checks out if we instead inherit most of our interfaces from ViewProps, which includes the testID attribute we can use in place of className. Got to love Typescript.

Power with nesting

A major appeal about styled is how much it cleans up the detail in your main JSX component. If we decided we’d like an InnerBox with a margin of 10 px smaller vertically and horizontally to the outer box dimensions, we just create another style based on View and simply nest the style tag within the BoxRoot style tag:

Back to addressing our padding values from ThemeInterface.

We can spread out props into the sytled component via {…props}, instead of explicitly setting them via individual attributes like className={props.className}. Notice that the padding attribute is still able to be set from a value in our theme prop, just like in the original example.

You may also be able to use props when styling StyleSheet with a simple arrow function, but it seems more inline with Typescript to use type assertion via <IBoxProps>, to let the styled component know what properties it can expect.

This is immensely convenient. If you’re a good advocate of atomic design, you can ensure that a particular style only gets what it needs.

Say for example if our Box was quite extensive, perhaps it has some animation values, rounded corners, border thickness, background and gradient colors, etc. The individual InnerBox may not need to access all of that, it may only need the background color. The example below takes our Box component to the extreme to demonstrate this:

Alternatively, because IInnerBoxProps shares props with the main component’s interface, we could just define the shared props in IInnerBoxProps and inherit IBoxProps from there instead of ViewProps.

interface IInnerBoxProps extends ViewProps {
theme: ThemeInterface;
backgroundColor: string;
}
interface IBoxProps extends IInnerBoxProps {
borders?: boolean;
borderThickness?: number;
roundedCorners?: number;
backgroundGradientColors?: string[];
animationSpring?: boolean;
animationSpringDuration?: number;
}

Adding default values

When using StyleSheet, we found that a lot of our props were for the actual style. Therefore we used defaultProps a lot to define default values. Now that we use styled components, it’s reduced the need for it a lot — our defaultProps are now just used exclusively for anything related to a component, instead of the styles that a component uses.

Because we can now reference props right from within our styles, we use default values right in line with those style definitions.

Say for example, if we wanted our Box to set to a default color based on the theme. But also allow the owning component to set it to a color of its choice if it wanted to.

With the previous modifications, we can do that by using the backgroundColor prop on the public interface, and the prop will be passed into the BoxRoot style via the spread. We’ve set our backgroundColor default to transparent.

We’ve also set some default values for width, height, and padding.

Alternatively, you can still use defaultProps exactly the same way to how you’d regularly use it with a functional component, and it can simplify your inline conditionals within the actual styled component:

Our Box component can now simply be customized by setting these props (or just the theme prop, seeing as the remaining attributes are optional) like so:

<Box theme={theme} size={300} backgroundColor={‘salmon’} />

Inheriting reusable styles

I touched on using inheritance above, in regards to how we define our interfaces. But we can also apply it to overall styled components.

A lot of react projects have a typography file (or a theme interface) to specify their font attributes. Bungalow is no different. Let’s say that you have a base font style that you want to set up for Text related components.

At Bungalow we’ve been defining our font styles that apply to our theme in one file, that exports styled-components. We have one overall Text style that defines basic attributes that are common enough to be used in all labels. Then we inherit this style from our H1 to H6 styles that we define (or any other styles that need it)

This isn’t exactly what we have, but this is a simplified example that builds on the context of the Box examples above:

Limitations to be aware of

There aren’t that many limitations with using styled-components that we’ve come across, given the context that they were made for anyway.

One that comes to mind though is that there’s no way to combine multiple styles in one styled component (that we know of). When you think about how React is heavily biased towards using composition though, this is fine.

For instance, if you’ve created a button component that fulfills the role of always being bordered (BorderButton), and another button that fulfills the role of always having a background color (ColorButton). Then one day you want to combine both and have a bordered + background color button, you can’t do something like this:

const BorderButton = styled.Button`
...
`
const ColorButton = styled.Button`
...
`
const BorderedColorButton = styled(BorderButton, ColorButton)`
...
`

This is probably a good thing, as it conceptually resembles multiple inheritance. While any passionate React advocate would argue that border and backgroundColor should be props instead on one button component — and I tend to agree — the above scenario is for example purposes only (perhaps there’s a better one that is more practical).

It’s not a dealbreaker that styled-components limits us from invoking programming practises that aren’t ideal.

Overall, styled-components have made building our mobile app in React Native a lot more enjoyable. For the separation of concerns between styling and composition, improved readability, and the flexibility it provides in defining styles based on the props of the main component.

Now, if there was only a GUI for React that provided the benefits of Xcode storyboards but using styled-components instead of auto layout constraints, that would be ideal! #pipedream 😉

--

--

Mike Post
Bungalow Garage

Founder and Engineer at FitFriend. Runner, Orienteer. Life is about evolution and I want to contribute to that