We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through JavaScript Works average a 15% increase in salary 🚀

Blog hero image

React sub-components Part 3: Whitelisting sub-components with flow

Maxime Heckel 17 May, 2018 | 4 min read

Adding more control to the sub-component pattern with Flow types

This small post is the continuation of my second article about sub-components. To fully understand this article please read that first

In Part 2 of my React sub-components series, we saw how using Context could greatly improve the original pattern I’ve described in my first post. However, one regression came along with this improvement and in this article we’ll bring a solution to it thanks to static typing.

Current Issue

The sub-component pattern described in the previous post addressed a few issues of the original implementation, but introduced one regression: children that are not properly defined as sub-components are still being rendered.

Originally, the findByType util was being used to render our sub-components and would skip any unknown sub-components. The code snippet below shows this regression. You can give it a try by pulling the example project here and following the instruction in the README to run it. You can try switching the imports of <App/> in index.js to see how both implementation defer.

  <Article>  
        <Article.Title />
        {/* 
          This will render if we use the sub-component pattern described in part 2,
          but will be skipped with the one described in part 1 (the findByType util 
          mentioned in the post will simply not render "<div></div>" as it's not
          a known sub-components of <Article/>.
        */}
        <div>Hello World</div>
         <Article.Metadata />
        <Article.Content />
      </Article>

This regression breaks one of the main advantages of using sub-components: narrowing the scope of what can be rendered within a given component to make sure it is used properly and avoiding a messy codebase.

To fix this, as I mentioned at the end of the previous article, I decided to use static typing. The main idea here is to provide a specific type for the Article component, so that only a given list of components (i.e. our sub-components) will be rendered within it.

Flow to the Rescue

Let’s see how static typing can fix the main caveat of the sub-component pattern that is implemented with contexts. I’m going to use Flow here to handle my types.

The gif below shows the actual implementation of using static typing to whitelist the sub-components of Article . You can see that before adding <div>Hello</div> as a child of Article, running Flow against my codebase returns no errors. However, once I add this extra piece of code, Flow will output the following error:

Cannot create Article element because in property type of array element of
property children:
 • Either div [1] is incompatible with typeof Title [2].
 • Or div [1] is incompatible with typeof Metadata [3].
 • Or div [1] is incompatible with typeof Content [4].

1_HqiRL5mQ6tRCONrSivQ5cQ.gif

As you can see, Flow is aware of the type of our sub-components (and of any children of Article ), and reports that div is not compatible with one of these types.

To make sure to catch whether someone misuses Article in our codebase, we can simply add Flow as a step in our CI pipeline. Additionally, there are many editor extensions available to highlight whenever a component is not use correctly given its Flow type.

How to achieve whitelisting with Flow

First, we need to add Flow to our project. For that, I recommend following this guide. Once done, running flow at the root of the project should output no errors since we haven’t typed anything in our codebase yet.

Then we’ll need to do some modifications to our Article.js file. First, we’ll have to change any sub-components declared as a functional component to a full class. This is due to the fact that classes have their own type but functional components do not. If we want to whitelist children, this is going to be the only limitation here.

Then, we’ll have to declare the types¹ of our sub-components, i.e. the children of Article . For that we’ll declare a type ArticleSubComponent which will be of type Title or of type Metadata or of type Content .

Finally, we need to associate that type to the children prop of Article. Our component can have either 1 or more children, thus the type should either be an array of React elements of type ArticleSubComponent if Article has 2 or more children or a single React element of type ArticleSubComponent if it has 1.

The code snippet below shows the resulting component:

// @flow
import * as React from 'react'

// This creates the "Article Context" i.e. an object containing a Provider and a Consumer component
// $FlowFixMe
const ArticleContext = React.createContext();

// We use classes here instead of functional components for our sub-components
// so we can define a type for each one of them

// This is the Title sub-component, which is a consumer of the Article Context
class Title extends React.Component<{}> {
  render() {
    return (
      <ArticleContext.Consumer>
        {({ title, subtitle }) => (
          <div style={{ textAlign: 'center' }}>
            <h2>{title}</h2>
            <div style={{ color: '#ccc' }}>
              <h3>{subtitle}</h3>
            </div>
          </div>
        )}
      </ArticleContext.Consumer>
    );
  }
};

class Metadata extends React.Component<{}> {
  render() {
    return (
      <ArticleContext.Consumer>
        {({ author, date }) => (
          <div
            style={{
              display: 'flex',
              justifyContent: 'space-between'
            }}
          >
            {author}
            {date}
          </div>
        )}
      </ArticleContext.Consumer>
    );
  }
};

class Content extends React.Component<{}> {
  render () {
    return (
      <ArticleContext.Consumer>
        {({ content }) => (
          <div style={{ width: '500px', margin: '0 auto' }}>{content}</div>
        )}
      </ArticleContext.Consumer>
    );
  }
};

// The ArticleSubComponent type is either of type Title, Metadata or Content
// any other type is not supported by this component, and will result in an
// error in flow if use as a child of Article
type ArticleSubComponents = typeof Title  | typeof Metadata | typeof Content;

type Property = string | Node;

type ArticleObject = {
  title: Property,
  subtitle: Property,
  author: Property,
  date: Property,
  content: Property,
};

// Here we type the props of our Article component
type Props = {
  value: ArticleObject,
  // We assign the children prop its type: either an array of React elements of
  // type ArticleSubComponents (if Article has 2 or more children) 
  // or just a React element of type ArticleSubComponents (if Article
  // only has 1 child)
  children: Array<React.Element<ArticleSubComponents>> | React.Element<ArticleSubComponents>, 
};

// This is our main Article components, which is a provider of the Article Context
const Article = (props: Props) => {
  return (
    <ArticleContext.Provider {...props}>
      {props.children}
    </ArticleContext.Provider>
  );
};

Article.Title = Title;
Article.Metadata = Metadata;
Article.Content = Content;

export default Article;

There are surely other solutions to address this issue, but this is the one I’m exploring as it uses dependencies and patterns that I’m already using in most of my projects.

Feeling like playing with Flow types and sub-components? I’ve made available this specific example on a branch named flow on the same repository that I’ve used as an example for the previous post. Check it out here!

If you liked this article don’t forget to hit the “rocket” button and if you have any other questions I’m either reachable on Twitter, or from my website.

[1] The type reference doc from the Flow website was very useful when looking to type my classes https://flow.org/en/docs/react/types/#toc-react-element

Originally published on medium.com

Author's avatar
Maxime Heckel
Engineer @Docker. 20 something French coffee addict new to the Bay Area. Running code, roads and climbing walls.
    JavaScript
    CSS
    Go
    HTML
    OCaml
    Python
    React
    Redux
    GraphQL
    NodeJS
    Docker
    Swarm
    Kubernetes

Related Jobs

Related Issues

viebel / klipse-clj
viebel / klipse-clj
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
  • $100
viebel / klipse
  • 1
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
  • $80
viebel / klipse
  • Open
  • 0
  • 0
  • Advanced
  • Clojure
  • $80
viebel / klipse
  • Started
  • 0
  • 2
  • Advanced
  • Clojure
  • $180
viebel / klipse
  • Open
  • 0
  • 0
  • Intermediate
  • Clojure
viebel / klipse
  • Started
  • 0
  • 1
  • Intermediate
  • Clojure
  • $80

Get hired!

Sign up now and apply for roles at companies that interest you.

Engineers who find a new job through JavaScript Works average a 15% increase in salary.

Start with GithubStart with Stack OverflowStart with Email