Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorators with React Components

I am very excited about the ability to use the @myDecorator syntax (using babel). I am trying to decorate one of the life cycle functions, specifically componentWillMount, and check the props and context of the component within the decorator. However, I seem to not be able to access either the props or context. I am not sure if this is sort of an anti pattern or if I am just going about this wrong.

Small Example:

// TestComponent.jsx
import checkProps from 'checkProps.js';

class TestComponent extends React.Component {
    @checkProps
    componentWillMount() {
        // Do something.
    }

    render() {
       return <div>My Component</div>
    } 
}

and

// checkProps.js
export function checkProps(target) {
    console.log(target.props);
}

I have also tried arrow functions for the decorator and checking this, but I don't think decorators compose things to work that way.

I have also tried making my decorator a factory and passing in this.props and this.context but this is undefined when decorating a component life cycle function.

like image 268
Shawn Avatar asked Mar 29 '16 13:03

Shawn


1 Answers

ES7 ECMAScript Decorators have the same API as Object.defineProperty(target, name, descriptor) so the target parameter is your class at the time your decorator is applied, not at the time it is called by React. To influence what the decorator is doing at runtime you have to modify descriptor.value that is the actual function that is decorated:

export function checkProps(target, name, descriptor) {
    // Save the original method, it'd be a good idea
    // to check that it really is a function
    const decoratee = descriptor.value;

    // Modify the descriptor to return our decorator
    // function instead of the original
    descriptor.value = function decorated(...args) {
        // Do something before ...
        console.log('before: ' + name, this.props, args);

        // Calling the decorated method on this object
        // with some arguments that could have been 
        // augmented by this decorator
        decoratee.apply(this, args);

        // Do something after ...
        console.log('after: ' + name);
    };
} 

// Or if you wanted to use a factory function to supply some configuration
// to the decorator you can do it this way

export function checkProps(config) {
    return function configurableCheckProps(target, name, descriptor) {
        // Save the original method, it'd be a good idea
        // to check that it really is a function
        const decoratee = descriptor.value;

        if (config.someOption) {
            // Do something based on the config
        }

        // Modify the descriptor to return our decorator
        // function instead of the original
        descriptor.value = function decorated(...args) {
            // Do something before ...
            console.log('before: ' + name, this.props, args);

            if (config.someOption) {
                // Do something based on the config
            }

            // Calling the decorated method on this object
            // with some arguments that could have been 
            // augmented by this decorator
            decoratee.apply(this, args);

            // Do something after ...
            console.log('after: ' + name);
        };
    };
} 

Here's an example that also modifies the behavior of a method that demonstrates this more thoroughly.

Hope this helps. Happy coding!

EDIT: As a commenter pointed out, decorators are not part of ES7 but as of March 2016 the proposal is still in Stage 1, my bad

EDIT2: As of June 2018 the proposal is in the proposal is still in Stage 2, we're getting closer

like image 108
mfeineis Avatar answered Sep 18 '22 08:09

mfeineis