Skip to content

Instantly share code, notes, and snippets.

@sebmarkbage
Last active January 31, 2024 18:33
Star You must be signed in to star a gist
Save sebmarkbage/ef0bf1f338a7182b6775 to your computer and use it in GitHub Desktop.
Higher-order Components
import { Component } from "React";
export var Enhance = ComposedComponent => class extends Component {
constructor() {
this.state = { data: null };
}
componentDidMount() {
this.setState({ data: 'Hello' });
}
render() {
return <ComposedComponent {...this.props} data={this.state.data} />;
}
};
import { Enhance } from "./Enhance";
class MyComponent {
render() {
if (!this.data) return <div>Waiting...</div>;
return <div>{this.data}</div>;
}
}
export default Enhance(MyComponent); // Enhanced component
@gyzerok
Copy link

gyzerok commented Jun 16, 2015

@sebmarkbage, @ALL What do you think about following implementation?

export function Enhance(ComposedComponent: Component, Mixin: Component): Component {
  return class extends Mixin {
    render() {
      return <ComposedComponent {...this.props} {...this.state} />;
    }
  }
}
import React, { Component } from 'react';
import { Enhance } from './Enhance';

class SomeMixin extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = { data: null };
  }
  componentDidMount() {
    this.setState({ data: 'Hello' });
  }
}

class SomeView extends Component {
  render() {
     return <div>{this.props.data + ', world!'}</div>;
  }
}

export default Enhance(SomeView, SomeMixin);

@gyzerok
Copy link

gyzerok commented Jun 22, 2015

If somebody is intrested, I've end up with the following implementation of Enhance:

export function Enhance(ComposedComponent: Component, mixins: Array<Component>): Component {
  return mixins.reduce((ComposedComponent, Mixin) => {
    return class extends Mixin {
      render() {
        return <ComposedComponent {...this.props} {...this.state} />;
      }
    }
  }, ComposedComponent);
}

@xtrasmal
Copy link

xtrasmal commented Sep 5, 2015

A lot of implementations, not enough discussion.

@namuol
Copy link

namuol commented Sep 16, 2015

HOCs are great and I use them all the time, but let's be careful not to conflate them with decorators.

That is, HOCs are not decorators, especially in the canonical sense of the term.

A HOC using the above Enhance method implicitly "masks" any properties defined on the original class (static, prototype, or instance methods/properties are not part of the wrapped API).

Canonical decorators leave the original API surface of its input intact and only adds behavior:

export default function hasBar(Base) {
  return class extends Base {
    bar() { return 'baz'; }
  }
}
export default function hasQuux(Base) {
  return class extends Base {
    quux() { return 'biz'; }
  }
}
import hasBar from './hasBar';
import hasBar from './hasQuux';

@hasBar
@hasQuux
export default class Foo {
  answer() { return 42; }
}
import Foo from './Foo';

let f = new Foo();

f.quux(); // "biz"
f.bar(); // "baz"
f.answer(); // 42

If you're careful, this approach can work nicely with components and maintain the original component's API-surface:

export default function doesSomethingOnMount(Component) {
  return class extends Component {
    static displayName = Component.displayName || Component.name;

    componentDidMount() {
      super.componentDidMount && super.componentDidMount();
      // do your stuff here
    }
  };
}
import doesSomethingOnMount from './doesSomethingOnMount';

@doesSomethingOnMount
class Foo extends Component {
  bar() { return 'baz'; }
  // ...
}
<Foo ref="foo" onClick={() => { alert(this.refs.foo.bar()); }} />

Of course, in many (probably most) cases it might make more sense to use HOCs, but this is a useful pattern if your component needs to maintain its original API surface when enhanced.

If your extension adds a new propType to your component, for instance, consider using something like Object.assign to extend the original version of propTypes, and politely warn the user if you override any of their existing propTypes:

export default function hasFooProp(Component) {
  const displayName = Component.displayName || Component.name;

  if (Component.propTypes && Component.propTypes.foo) {
    console.warn(`Warning: `hasFooProp` is overriding the original \`displayName.propTypes.foo\`.`);
  }

  return class extends Component {
    static displayName = displayName;

    static propTypes = Object.assign({}, Component.propTypes, {
      foo: React.PropTypes.string,
    });
  };
}

However, if you find yourself carefully overriding/merging many parts of the original component's API surface like with the above example, it's a good sign that a HOC is the more appropriate solution.

Cheers~

@chentaixu
Copy link

@namuol Thanks for your detailed explaination! I just ran into this issue and I didn't even realize it before. This gets especially tricky when you have something like:

@myEnhance
class Cat {
    static meow = event => {
        return 'meow!';
    }
    render (){
        return (<div>{{Cat.moew()}}</div>);
    }
}
export default Cat;

function myEnhance {
    return (SomeComponent) => class EnhancedComponent extends React.Component {
        render() { 
            return <Cat />;
        }
    }
}

Here, the 'Cat' class is actually the EnhancedComponent class and the render method in Cat class would not work!

@arush
Copy link

arush commented Sep 19, 2015

@namuol thanks for pointing out those gotchas, great advice.

An alternative to HOCs and decorators, although not idiomatic React, is to use the excellent react-stampit which works extremely well for solving the mixin issue, as it favors composition over inheritance

@acdlite
Copy link

acdlite commented Oct 8, 2015

Yesterday I released Recompose, which takes the higher-order components concept and runs with it. I've been writing React components in this style for a while now, and I think it covers a vast majority of use cases. Certainly there are other things React can do to improve its API — I'm especially interested in efforts to externalize the state tree — but it's impressive how much can be accomplished using the basic component architecture. Really speaks to how much React has gotten right, even from the start.

https://github.com/acdlite/recompose

@koresar
Copy link

koresar commented Oct 11, 2015

This Gist looks like reinvention of the classic Dependency Injection design pattern.

class Car
{
    private Engine;
    private SteeringWheel;
    private Tires tires;

    public Car(Engine engine, SteeringWheel wheel, Tires tires)
    {
        this.Engine = engine;
        this.SteeringWheel = wheel;
        this.Tires = tires;
    }
}

It's getting messy in complex (read most) apps where an object (read component) dependency tree is hard to reason about.

Just imagine your HOCs depend on other HOCs which depend on another HOC which depend on few other HOCs ... and so on. That is tight coupling - the forever problem the classical inheritance suffer from.

@sebmarkbage
Copy link
Author

It is not quite the same because you can chain HoC where each one is independent. It is easy to screw this up though.

This pattern was designed specifically for things like Relay which just pass props straight through. It has since been picked up and abused for other patterns.

@koresar
Copy link

koresar commented Oct 11, 2015

It is easy to screw this up though.

Yep. I assume people will screw up using the approach.

This pattern was designed specifically for things like Relay ...
It has since been picked up and abused for other patterns.

This reminds me the MVC pattern. It was designed for a single small purpose, but blown out to be an architecutre because people abused it.

@franleplant
Copy link

What's the difference between HOC and this.props.children aside of the syntax? A parent component can modify the children's props, abstract state, and control the way the child instance renders. It'll be interesting to know what are the things both patterns have in common and where do they differ

@JoeGrasso
Copy link

I have no idea what you all above posted because none of them worked. Did you test them? This works .......

 import React from 'react';
 import ReactDOM from 'react-dom';
 import { MyEnhance } from "./enhance";

 @MyEnhance
 class MyComponent extends React.Component {
   render() {
     if (!this.props.data) return <div>Waiting...</div>;
     return <div>{this.props.data}</div>;
   }
 };

 ReactDOM.render(<MyComponent />, document.getElementById('root'));

 import React, { Component } from "React";

 export var MyEnhance = ComposedComponent => class extends Component {
   constructor() {
     super();
     this.state = { data: null };
   }

 componentDidMount() {
     this.setState({ data: 'Hello You' });
   }

 render() {
     const temp = this.state.data;
     return <ComposedComponent {...this.props} data={ temp } />;
   }
 };

@passcod
Copy link

passcod commented Jan 23, 2016

@JoeGrasso ES6 changed in between the original post and your reading, thus it all did work just fine at the time. See about halfway through the thread for someone commenting about pretty much the same thing you did.

@tlrobinson
Copy link

Thoughts on using something like static displayName = "HigherOrderComponentName["+(Component.displayName || Component.name)+"]"; so it shows up like <HigherOrderComponentName[ComposedComponentName] ...> in the dev tools?

@taijiweb
Copy link

domcom(https://github.com/taijiweb/domcom) can have true inheritance.

@bySabi
Copy link

bySabi commented May 6, 2016

This are minimal skeleton for higher order components

If your higher order component need state or lifecycle methods use this:

const hoc = C => class _hoc extends React.Component {
    render() {
      return <C { ...this.props }/>;
    }
  }

For debugging purpose with React Tools I match function name with class name, Ex: hoc match _hoc. This way I know what is a "real" component and what a HOC, it looks like a function.

If you don´t need state or lifecycle methods on your HOC this snip is more functional:

const hoc = C => function _hoc(props) {
    return <C { ...props }/>;
  }

Same like first snip, I use a named function _hoc maching hoc for debugging purpose.

@mmollaverdi
Copy link

I've written about some real world examples of higher order components we use in our application here:
http://techblog.realestate.com.au/reactjs-real-world-examples-of-higher-order-components/

Comments are more than welcome

@MerlinYu
Copy link

MerlinYu commented Jun 3, 2016

I use this way
var MyComponent = Enhance(class extends Component {
render() {
if (!this.data) return

Waiting...
;
return
{this.data}
;
}
});
export default MyComponent; // Enhanced component

But it meets a problem "Minified exception occurred;use the non-minfied dev environment for the full error message and additional helpful warnings." How to deal with this probleam?

@jeromepl
Copy link

I created a forked showing how I used this with ES7 decorators: (Also fixes a few issue with this gist)
https://gist.github.com/jeromepl/2f7df563f273563261690221c22aa0af

@seeden
Copy link

seeden commented Jun 24, 2016

I created two packages which you can use for creating of high-order components.
With react-provide-props you can easily create high-order component which will add props to other components. It will create state-less high-order compoment.
Second package react-high-order-provider you can use for state-full high-order components where you can define your own logic.

Both libraries will create function compatible with ES7 decorators as well.

@Musbell
Copy link

Musbell commented Jul 9, 2016

Hey guys! you can checkout my example.

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

let HOC = WrappedComponent => {
return class extends Component{
constructor(props){
super(props);
this.state = {
name : ' '
};
this.onNameChange = this.onNameChange.bind(this)
}
onNameChange(e){
this.setState({
name : e.target.value
})
}
render(){
const newProps = {
value : this.state.name,
onChange : this.onNameChange
};
return <WrappedComponent {...this.props} {...newProps} {...this.state}/>
}
}
};

let App = HOC((props) => {
return(
<div> <input type="text" {...props}/> <p>{props.name}</p> </div>
)
});

ReactDOM.render(
<App />,
document.getElementById('root')
);

@Musbell
Copy link

Musbell commented Jul 9, 2016

@seeden
I love the libraries, they are both great 👍

@stiofand
Copy link

stiofand commented May 4, 2017

Interesting examples, is there any particular advantage of attempting to enforce OO over HOC? Anyhow, fun to follow the thread. Pedantic point of note, why in all the ES6 / ES7 examples are there still references to "var"? Also arrow functions implicitly return whatever is after them, so if no multi-expressions are needed, then no need for the curlies!

Anyhow.. I digress....

@rwieruch
Copy link

I love this gist, because it shows a simple example for a higher order component.

I am not sure if this is the perfect place, but maybe people are interested to read a more elaborated gentle introduction to higher order components when they come across this gist looking for HOC examples.

@Ramyace4455
Copy link

Hello everyone, I came across a good blog which is about React Higher Order components. It was completely awesome and explained in well manner. Have a look into it: Link
I hope you will learn knew content about React HOCs.
Regards,
ReactJS Online Training

@Musbell
Copy link

Musbell commented Jun 11, 2017

Thanks for the links 👍 @rwieruch @Ramyace4455

@kiransiluveru
Copy link

is this the first ever written HOC ?

@danny-andrews-snap
Copy link

Wow, the React community really flubbed on terminology here. In this gist, the component utilizing what we call the HoC is called the HoC, whereas the thing we call the HoC is called an "Enhancer." This makes way more sense, as a "HoC," as it is known today, is not itself a component at all. Sad!!

@ivenxu
Copy link

ivenxu commented Nov 24, 2018

Not quite see the difference between HoC and old decorator pattern. I would say HoC is a nice react implementation of decorator pattern.

@dexygen
Copy link

dexygen commented Jan 14, 2019

Your constructor needs super(props) as the first line

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment