Enforcing Method Presence in React Components with Flow

Recently I was on Flow’s Gitter when someone asked the following question:

How can I type that I want a function argument to be a React component with fooBar static method on it?

This question made me remember that a couple months ago, I’d had a similar problem (albeit without the static requirement). I’ll explain how to ensure the method is present when composing higher order components, but expect that you know how to type generic HOCs as described in the Flow documentation.

Essentially, the main idea is to create an interface that contains the method you want to have and union it with the component passed into the function that generates the HOC.

interface Enforced {
  enforcedMethod(): void;
}

function<Props, State>(
    component: Class<React.Component<Props, State> & Enforced>
): Class<React.Component<Props, State> { ... }

Basically, this says, “Give me a class that’s a React component that also has an instance method called enforcedMethod and I’ll give you back a React component with the same props and state types.”

As a more complete example, suppose we wanted to build a higher-order component that fired the onClickedOutside method of a component when the user clicks anywhere outside the resulting element. It would be pretty catastrophic if we applied our HOC to a component that didn’t have an onClickedOutside, so we’ll make Flow warn us if it isn’t present.

import * as React from 'react'
import ReactDOM from 'react-dom'

interface HasClickOutside {
  onClickedOutside(e: Event): void;
}

function ClickOutside<Props, State>(
    Wrappable: Class<React.Component<Props, State> & HasClickOutside>
): Class<React.Component<Props, State>> {
  return class extends Wrappable {
    componentDidMount = (): void => {
      document.addEventListener(
        'click',
        this.outsideClickHandler,
        true
      )
    }

    componentWillUnmount = (): void => {
      document.removeEventListener(
        'click',
        this.outsideClickHandler,
        true
      )
    }

    outsideClickHandler = (e: Event): void => {
      const domNode = ReactDOM.findDOMNode(this);
      const clickedOutside = !domNode ||
        ((e.target instanceof Node) &&
          !domNode.contains(e.target))
      if (clickedOutside) {
        this.onClickedOutside(e)
      }
    }
  }
}

// No errors!
ClickOutside(class extends React.Component<{}> {
  onClickOutside = (e: Event): void => { ... }
})

// Error: property `clickedOutside` of HasClickOutside.
// Property not found in ...
ClickOutside(class extends React.Component<{}> {
  // no onClickOutside method
})

With this HOC, we can make a component set something within its state or take some other action when we click outside, and Flow will warn us if we don’t tell the HOC what to do when we click outside the element!

Now, let’s turn back to the original question of how to enforce a static method on a component. One would think that it would be as simple as adding static to the interface, but you and I know better. Luckily, all we need to do is tell Flow that our function expects a class that is a React component that has a callable signature.

interface Enforced {
  enforcedMethod(): void;
}

function<Props, State>(
    component: Class<React.Component<Props, State>> & Enforced
): Class<React.Component<Props, State> { ... }

You’ll note that all we had to change from our first example was moving the type union outside of the Class<...> expression. Now Flow will enforce that we pass in something with a static enforceMethod!

SUBSCRIBE TO BLOG UPDATES

Picture of Mark Siebert

Mark Siebert

Author Bio

Mark is a full-stack engineer who cut his teeth on professional software development at Lucid Software where he specialized in building scalable backend systems. He's a huge fan of open source software and is one of the primary authors of Relate (database access library) and Cumulus (AWS infrastructure management). Mark loves watching shows with his wife, studying Chinese, and hunting Minecraft chickens.

What is Blue Matador?

Blue Matador is the AI-powered DevOps monitoring platform that provides real-time, predictive alerts that help your team decrease downtime and increase customer confidence in your brand. Learn more

Our Monitoring Products

Watchdog is the free server monitor that sends you and your team proactive system vitals alerts, proactively notifying you of all the metrics you need to know to prevent downtime. Install for free


Lumberjack is the AI-powered centralized log management tool that proactively warns your DevOps team of impending server and app issues that affect uptime. Try free for 14 days