Creating Declarative React Components with CSS-in-JS and TypeScript

Apr 15, 2018

Why?

We'll talk about how to alleviate those issues in this post.

Component Creation

Creating React components is pretty simple to do.

import * as React from  "react";

export default class App extends React.Component {
    render () {
        <h1>Hello world!</h1>
    }
}

However, App isn't really declarative. What happens if we need to update the component logic to contain some more complicated rules, like conditional rendering based on props? Then you have:

import * as React from  "react";

interface OwnProps {
    title: string;
}

export default class App extends React.Component<OwnProps, {}> {
    render () {
        const { title } = this.props;
        if (title !== null && title !== undefined) {
            return (
                <h1>Hello {title}!</h1>
               );
        }
        
        return null;
    }
}

Only one prop, and we already have a decent amount of logic built into the render function. Suppose we had a component that would take in more than one prop? What if one of those props was typed to a more complex data object structure? That isn't scalable. It can be easy to "just write a component that does the thing" but when you work on a team of multiple developers that may maintain your component, that's when problems arise.

Let's talk about the technical implementation of the code above:

The last two things are huge in my opinion. I've been noticing more of a trend towards a desire of positive DX (developer experience) in projects. I've worked on a project where the code was terrible, difficult to grok, and updating it would break a different component elsewhere in the project. It made me simply not want to work on it anymore. I've found that establishing declarative components with css-in-js solutions like styled-components or emotion in React has helped a lot. Let's use these to refactor our component above.

Styled components (CSS-in-JS)

Inside a separate file (I like calling it style.ts in the same directory):

import styled from "styled-components";

export const Title = styled.h1`
    color: cornflowerblue;
    font-family: Helvetica;
`;

Back to App:

import * as React from  "react";
import { Title } from "./style";

interface OwnProps {
    title: string;
}

export default class App extends React.Component<OwnProps, {}> {
    render () {
        const { title } = this.props;
        if (title !== null && title !== undefined) {
            return (
                <Title>Hello {title}</Title>
               );
        }
        
        return null;
    }
}

Nothing really changed, but now our <Title> component is declarative. It's immediately obvious what it's doing and it's role within the parent component. Let's take this a step further and think about a hypothetical application that uses unordered lists, but they are styled differently and have different use cases.

In our styles file:

import styled from "styled-components";

export const Title = styled.h1`
    color: cornflowerblue;
    font-family: "Helvetica" serif;
`;

export const List = styled.ul`
    color: rebeccapurple;
`;

export const ListItem = styled.li`
    font-weight: bold;
    &:hover {
        text-decoration: underline;
        cursor: pointer;
    }
`;

export const GroceryList = List.extend`
    color: green;
`;

export const GroceryItem = ListItem.extend`
    font-family: "Arial", sans-serif;
`;

Suppose we're building a grocery shopping app. We have a normal List and ListItems components that we can share across different sections, and a GroceryList and GroceryItem component that are more declarative as to their function. This makes the developer experience much easier to read:

import * as React from "react";
import Grocery from "./models";
import { GroceryList, GroceryItem } from "./style";

interface OwnProps {
    groceries: Grocery[];
}

const GroceryContainer: React.SFC<OwnProps> = ({groceries}) => (
    <GroceryList>
        {
            groceries.map((item, index) => <GroceryItem key={index}>{item.name} {item.price}</GroceryItem>)
        }
    </GroceryList>
);

export default GroceryContainer

Relating code to your business

Reading through this component makes much more sense than reading through a component that renders out a ul and maps through li elements. We are relating a React component to a business rule; this also serves to decouple your React components from being monolithic, as these styled components can be shared across your application declaratively and effectively. This improves the developer experience and allows developers to work towards solving business problems rather than grinding away trying to grok a complex React component.

Faster development work

By coupling our component styles to a business rule, we can much more easily share them across different applications. At EF Go Ahead Tours, we use a subset of styled components that adhere to our overall branding and we have a set of styled components for each individual app that we write. This makes it much easier to achieve a unified branding across multiple web apps using React and TypeScript.

Adding to the power of CSS-in-JS solutions is an automatic vendor prefixer. Developers can confidently start coding and be sure that their code will be supported across a wide variety of browsers.

You can view the source code here if you've gotten lost.