blog.jakoblind.no

Real world Higher-Order Components (HOCs)

Trying to wrap your head around React HOCs? Want some real world examples to look at?

Then you’ve come to the right place!

If you are completely new to React HOCs, you should read my Simple explanation of Higher-Order Components (HOC) before moving on.

Add a hidden prop to your components

This HOC adds a hidden prop to your components. When hidden is set to true, your components becomes… hidden!

Usage

const HelloWorldWithHidden = withHiddenProp(HelloWorld)

// ...

<HelloWorldWithHidden hidden={true} />

HOC

export function withHiddenProp(WrappedComponent) {
  return class extends React.Component {
    render() {
      if (this.props.hidden) {
        return null
      } else {
        return <WrappedComponent {...this.props} />
      }
    }
  }
}

Full example

Full example on codesandbox.io

Add toggle functionality to your components

This HOC adds two props to your components: toggled and onToggle

  • toggled: a boolean that represents the toggle
  • onToggle: a function that toggles the toggled variable

Usage

const Button = ({ toggled, onToggle }) => (
  <button onClick={onToggle}>{toggled ? "It's ON! :)" : "It's OFF! :("}</button>
)

const ToggledButton = withToggle(Button)

// ...

<ToggledButton />

HOC

function withToggle(WrappedComponent) {
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.onToggle = this.onToggle.bind(this)
      this.state = {
        toggled: false,
      }
    }
    onToggle() {
      this.setState({ toggled: !this.state.toggled })
    }
    render() {
      return (
        <WrappedComponent
          onToggle={this.onToggle}
          toggled={this.state.toggled}
          {...this.props}
        />
      )
    }
  }
}

Full example

Full example on codesandbox.io

Show component when feature toggle is set

Feature toggles are a powerful technique to change behavior without deploying new code.

When using this HOC, your component will only be displayed when its feature toggle is set to true.

Usage

const HelloWorldWithToggle = whenFeatureToggled("helloWorld", HelloWorld)

// ...

<HelloWorldWithToggle />

HOC

// This might come from a global config file
const featureToggles = {
 helloWorld: true
};

function whenFeatureToggled(feature, WrappedComponent) {
  return class extends React.Component {
    render() {
      if (!featureToggles[feature]) {
        return null;
      } else {
        return <WrappedComponent {...this.props} />;
      }
    }
  };

Full example

Full example on codesandbox.io

Add a loading spinner while fetching data

A common UI pattern is showing a loading spinner while data is fetched asynchronously from the backend.

This HOC shows a spinner when this.props.data is null or undefined.

Usage

// Presentational component
const HelloWorldWithLoadingSpinner = withLoadingSpinner(HelloWorld)

// Conatiner component
class HelloWorldContainer extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data: null,
    }
    // In the real world, this is probably an Ajax call
    setTimeout(() => this.loadingComplete(), 2000)
  }
  loadingComplete() {
    this.setState({ data: "hello" })
  }
  render() {
    return <HelloWorldWithLoadingSpinner data={this.state.data} />
  }
}

HOC

function withLoadingSpinner(WrappedComponent) {
  return class extends React.Component {
    render() {
      if (!this.props.data) {
        return <Spinner />
      } else {
        return <WrappedComponent {...this.props} />
      }
    }
  }
}

Full example

Full example on codesandbox.io

Responsive components

If you need a responsive design you have two options: either use media queries with CSS or use JavaScript/React. There are pro and cons for each. If you go for the React solution you might want to use this HOC.

This HOC is based on this excellent solution for detecting mobile/desktop by Gosha Arinich

Usage

class HelloWorld extends React.Component {
  render() {
    return this.props.isDesktop ? (
      <h1>Hello world from Desktop</h1>
    ) : (
      <h1>Hello world from mobile</h1>
    )
  }
}

const HelloWorldResponsive = detectDesktopOrMobile(HelloWorld)

// ...

<HelloWorldResponsive />

HOC

function detectDesktopOrMobile(WrappedComponent) {
  return class extends React.Component {
    constructor() {
      super()
      this.state = {
        width: window.innerWidth,
      }
    }
    componentWillMount() {
      window.addEventListener("resize", this.handleWindowSizeChange)
    }

    // make sure to remove the listener
    // when the component is not mounted anymore
    componentWillUnmount() {
      window.removeEventListener("resize", this.handleWindowSizeChange)
    }

    handleWindowSizeChange = () => {
      this.setState({ width: window.innerWidth })
    }
    render() {
      return (
        <WrappedComponent isDesktop={this.state.width > 500} {...this.props} />
      )
    }
  }
}

Full example

Full example on codesandbox.io 

Combining HOCs

Now we have a bunch of small composable HOCs. We can combine many HOCs on one component like this:

const Button = ({ toggled, onToggle }) => (
  <button onClick={onToggle}>{toggled ? "It's ON! :)" : "It's OFF! :("}</button>
)

// Show a toggle button when it's feature toggled in
const ToggledButton = whenFeatureToggled("myToggleButton", withToggle(Button))

// ...

<ToggledButton />

As you can see, you combine HOCs by doing nested function calls. This can be a bit tricky to read and work with when having many HOCs:

hoc1(hoc2(hoc3(hoc4(Component))))

A solution to this is using a utility library included in Redux (and Recompose) that composes HOCs:

compose(hoc1, hoc2, hoc3, hoc4)(Component)

More readable and easier to work with!


tags: