1

Let's say I have the following React Component class:

class SayHello extends React.Component {
    constructor(props) {
        super(props);

        this.handleOnClick = this.handleOnClick.bind(this);
    }

    render() {
        return <div onClick={this.handleOnClick}>Click Me</div>;
    }

    handleOnClick() {
        console.log("clicked");
    }
}

What I want to do is create a higher order component that knows about the handleOnClick in SayHello but before calling SayHello's handleOnClick, I want it to execute some code I pass in first (i.e. I want to run code that logs something in my server).

Is there a React pattern for doing something like this?

EDIT:

I want to provide some more context here. I want my higher order component to be dynamic in terms of which methods to call. For example, sometimes it might be handleOnClick but other times it might be handleOnSomethingElse.

3
  • Is the custom "before component method" code dynamic as well? Commented Apr 13, 2017 at 19:36
  • Yes in the sense that I would want it to just mirror the method that's in the SayHello component. So if I'm trying to override SayHello's handleOnClick, then it'd be beforeHandleOnClick, and if its SayHello's handleSomethingElse, it'd be beforeHandleSomethingElse. Commented Apr 13, 2017 at 19:41
  • OK, I've added another answer. I left the first because I think it will help others. Commented Apr 13, 2017 at 20:08

2 Answers 2

6

A higher-order component is a function that takes a component argument and returns a new component.

This function returns a component with a decorated handleClick method:

// A higher-order component that runs some code before
// the given component's `handleClick` method
function wrapHello(componentClass) {
  return class wrapped extends componentClass {
    beforeHandleClick() {
      console.log("I run first!")
    }

    handleClick(...args) {
      this.beforeHandleClick()
      super.handleClick(...args)
    }
  }
}

This pattern is neat because it isn't specific to React at all; it's just a pure function. That means it's easy to test and reason about.

Here's a test harness that doesn't use React:

function wrapHello(componentClass) {
  return class wrapped extends componentClass {
    beforeHandleClick() {
      console.log("I run first!")
    }

    handleClick(...args) {
      this.beforeHandleClick()
      super.handleClick(...args)
    }
  }
}

class SayHello {
  handleClick() {
    console.log("handleClick")
  }
}

const WrappedHello = wrapHello(SayHello)
new WrappedHello().handleClick()

Sign up to request clarification or add additional context in comments.

1 Comment

thanks for this example - I was able to come up with something similar as well. The struggle I'm having is I want to make the wrapped component dynamic in terms of knowing which methods to decorate. I'll provide more context in my original question.
3

You need something like a dynamic mixin.

This higher-order component takes a Component class and an Object of decorator methods.

The HOC wraps each method that has a matching decorator. These methods call the decorator then call through to the original component method. Non-decorated methods are unchanged.

// Higher-order component
function decorateMethods(componentClass, decorators) {
  class decoratedClass extends componentClass { }

  Object.keys(decorators).forEach(decoratorName => {
    decoratedClass.prototype[decoratorName] = function(...args) {
      decorators[decoratorName].call(this, ...args);
      return componentClass.prototype[decoratorName].call(this, ...args)
    }
  })
  
  return decoratedClass
}

//
// Test 
//
class Component {
  foo() {
    console.log("foo")
  }
  
  bar() {
    console.log("bar")
  }
  
  baz() {
    console.log("baz")
  }
}

const DecoratedComponent = decorateMethods(Component, {
  foo() {
    console.log("before foo")
  },
  
  bar() {
    console.log("before bar")
  }
})

const d = new DecoratedComponent()
d.foo()
d.bar()
d.baz()

In this case the decorator methods exactly match the base class method names. If you want the decorator to use, e.g. beforeFoo instead, you could map method names with:

const methodName = decoratorName replace(/before(\w)/, (_, a) => a.toLowerCase())

1 Comment

Ah I see - I had a similar idea except I kept on trying to call super[decoratorName] and my console output threw an error about super being called outside of the class. This is super helpful - thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.