DEV Community

Cover image for Writing a styled HOC for React Native using Typescript
Lenilson de Castro
Lenilson de Castro

Posted on • Edited on

Writing a styled HOC for React Native using Typescript

I've always been very aligned with the styling philosophy of breaking the elements that form a component into smaller styled pieces with the opportunity to provide meaningful semantic names for those pieces.

function MyAccount(){
  return (
    <Container>
      <Header>
        <BackButton onPress={handleGoBack}>
          <ArrowLeftIcon />
        </BackButton>
        <HeaderText>My Account</HeaderText>
      </Header>
      ...
    </Container>
  );
}
Enter fullscreen mode Exit fullscreen mode

From my point of view it's easier to understand and maintain the components when the bits are named in such a way that it's clear what is the role of the component in the whole. It basically decouples what the component looks like from what it represents in the interface.

The goal is to create my own utility to achieve something similar to what styled-components does but using objects instead of css; as well as taking advantage of Typescript's intellisense to help compose the style of a component with auto completion.

The idea is quite simple, provide a way of creating simple named styled components which could be used on smaller projects and prototypes without having to install styled-components nor create runtime cost processing the css-ish styles.

Something like this, very straight forward:

const Heading1 = styled(Text, {
  fontSize: 24,
  fontWeight: 'bold',
  marginBottom: 12,
  marginTop: 24,
});
Enter fullscreen mode Exit fullscreen mode

Quick Note: This pattern of a function taking a component and potentially other arguments which then returns a new modified component is called Higher-order Components (HOC). The term is inspired by Higher-order Functions from functional programming which does a similar thing but with functions.

I started with a simple HOC to do the job without concerning types nor intellisense. The function should take the component and the style and return a wrapped component with the provided styles applied to it.

import React from 'react';

export function styled(
  Component,
  style,
) {
  return (props) => {
    return <Component {...props} style={[style, props.style]} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

Basically takes a component and a style argument and return another component that bypasses the props and combine styles with the style passed. Notice that our style comes before the props.style so that we can override styles on the high end by either providing new styles or using styled multiple times on the resulting component.

Done! Easy peasy lemon squeezy! Looks nice and it works. Now we can start adding types to create some constraints and, after all, we wanted to take advantage of intellisense as well.

First things first, our HOC can only style actual components, therefore, we can start by enforcing this on our function to make sure no one passes in invalid components creating uncontrolled behavior. React has the perfect type for that, which is, ComponentType<P> where P is the properties type of that component.

-import React from 'react';
+import React, { ComponentType } from 'react';

-export function styled(
+export function styled<P>(
-  Component,
+  Component: ComponentType<P>,
  style,
) {
-  return (props) => {
+  return (props: P) => {
    return <Component {...props} style={[style, props.style]} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

Notice the resulting component should as well be of properties type P because the HOC is supposed to return the "same" component but styled.

Now our function only accepts components. Which is good, but we can still mess up the style argument; besides, Typescript still does nothing to help us create the style object.

We can go next by addressing this particular problem which is, the style argument must be of the same type as the component's style. This, can be done by indexing the style property from our properties type P, that is, P['style'].

import React, { ComponentType } from 'react';

export function styled<P>(
  Component: ComponentType<P>,
- style,
+  style?: P['style'],
) {
  return (props: P) => {
    return <Component {...props} style={[style, props.style]} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

The problem now is Typescript is not happy because not all properties type P might have style on it. Which makes sense, right? We should only style components that can be styled, therefore, our component must have a style property.

So what we need to do to figure this constraint and consequently make typescript happy again is to enforce that the given component has a style property in its properties. Hence, we can state that P must have a style property on it.

"But what best defines a style property"? you might ask. Well, after some time digging the React Native type definitions I found out styles are defined by StyleProp<T> where T is the custom style definition of a component, that is: TextStyle, ViewStyle, etc; in our case, T is not important because we already have style type info from P['style'], hence, we can simply use unknown and it should work just fine.

import React, { ComponentType } from 'react';
+import { StyleProp } from 'react-native';

-export function styled<P>(
+export function styled<P extends { style?: StyleProp<unknown> }>(
  Component: ComponentType<P>,
  style?: P['style'],
) {
  return (props: P) => {
    return <Component {...props} style={[style, props.style]} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

And that's it! We are done! The function works and Typescript is happy again. Now intellisense of the style argument suggests valid style for the given component and we are good to go.

We can even refactor the function to make it more readable, I ended up with the code bellow which is the final version of my tiny styled HOC.

import React, { ComponentType } from 'react';
import { StyleProp } from 'react-native';

type StyleableProp = { style?: StyleProp<unknown> };

export function styled<P extends StyleableProp>(
  Component: ComponentType<P>,
  style?: P['style'],
) {
  return (props: P) => {
    return <Component {...props} style={[style, props.style]} />;
  };
}
Enter fullscreen mode Exit fullscreen mode

Here is some usage example:

import React from 'react';
import { View, Text } from 'react-native';

import { styled } from './styled';

const Container = styled(View, {
  flex: 1,
  alignItems: 'center',
  justifyContent: 'center',
});

const Heading1 = styled(Text, {
  fontSize: 24,
  fontWeight: 'bold',
  marginBottom: 12,
  marginTop: 24,
});

export default function App(){
  return (
    <Container>
      <Heading1>Hello, world!</Heading1>
    </Container>
  );
}
Enter fullscreen mode Exit fullscreen mode

In conclusion, I don't think by any extent this is a complete production ready solution to replace styled-components or other similar solutions but it's a good starting point to build my own tiny styling utility suite.

Next steps would be to figure ways to change attributes/props of the component, probably using a different HOC. Also, it would be interesting to figure a good theming by context solution, like styled-components but within the javascript/react way of doing things.

Regardless, it was a great experience exploring typescript's capabilities applied to React Native, you always end up learning really interesting stuff along the journey. Besides. it's good sometimes to build your own stuff.

I hope you've had a good read. Cheers!

Top comments (1)

Collapse
 
velsa profile image
Vels Lobak • Edited

Thanks for the inspiration )

You can check out this repo, which includes custom props, theming etc.

npmjs.com/package/styled-rn

It's fresh out of the oven, but works amazingly well for my own project :)