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 ponent 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 ponent to be dynamic in terms of which methods to call. For example, sometimes it might be handleOnClick
but other times it might be handleOnSomethingElse
.
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 ponent 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 ponent to be dynamic in terms of which methods to call. For example, sometimes it might be handleOnClick
but other times it might be handleOnSomethingElse
.
- Is the custom "before ponent method" code dynamic as well? – joews 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 ponent. 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. – wmock Commented Apr 13, 2017 at 19:41
- OK, I've added another answer. I left the first because I think it will help others. – joews Commented Apr 13, 2017 at 20:08
2 Answers
Reset to default 6A higher-order ponent is a function that takes a ponent argument and returns a new ponent.
This function returns a ponent with a decorated handleClick
method:
// A higher-order ponent that runs some code before
// the given ponent's `handleClick` method
function wrapHello(ponentClass) {
return class wrapped extends ponentClass {
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(ponentClass) {
return class wrapped extends ponentClass {
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()
You need something like a dynamic mixin.
This higher-order ponent 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 ponent method. Non-decorated methods are unchanged.
// Higher-order ponent
function decorateMethods(ponentClass, decorators) {
class decoratedClass extends ponentClass { }
Object.keys(decorators).forEach(decoratorName => {
decoratedClass.prototype[decoratorName] = function(...args) {
decorators[decoratorName].call(this, ...args);
return ponentClass.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())