Higher-Order Components in ReactJS - extend functionalities from data components

Viewed 242

I'm sorry for the stupid question, I'm learning React so I'm not expert at all.

I'm wondering if there is a way to extend my stateless components with some functionalities without creating always wrappers around them that they'll connect to the Redux store and pass data through props.

Is it the higher order component the best way to do that?

Let me explain better with a few lines of code. The example component below should call an API /favourites (POST) on the user click event in order to create a "Favourite" entity and it will call the same API (DELETE) in order to delete it.

This is the solution that I've realized:

const HOC_DISPLAY_NAME = 'HOCFavouriteIcon';
export default function HOCWrapperFavourite(FavouriteComponent) {

return class extends React.Component {

    static displayName = HOC_DISPLAY_NAME;

    constructor(props) {
        super(props);
        this.state = this.getDefaultState();
        this.bindMethods();
    }

    getDefaultState() {
        return {
            isFavourite: false,
            addFavouritePayload: null,
            removeFavouritePayload: null,
        };
    }

    render() {
        return (
            <DataProviderApiFavourites
                create={this.state.addFavouritePayload}
                createListener={this.favoriteCreateHandler}
                delete={this.state.removeFavouritePayload}
                deleteListener={this.favoriteDeleteHandler}
            >
                <FavouriteComponent
                    {...this.props}
                    {...this.state}
                    addFavourite={this.addFavouriteClickHandler}
                    removeFavourite={this.removeFavouriteClickHandler}
                />
            </DataProviderApiFavourites>

        );
    }

    addFavouriteClickHandler(visitorId, artId) {
            this.setState({
                addFavouritePayload: {visitorId, artId},
            });
    }

    removeFavouriteClickHandler(visitorId, favouriteId) {
            this.setState({
                removeFavouritePayload: {visitorId, favouriteId},
            });
    }

    favoriteCreateHandler(result){
        // do something when a favorite entity was created
    }

    favoriteDeleteHandler(result){
        // do something when a favorite entity was deleted
    }

    bindMethods() {
        this.addFavouriteClickHandler = this.addFavouriteClickHandler.bind(this);
        this.removeFavouriteClickHandler = this.removeFavouriteClickHandler.bind(this);
        this.favoriteCreateHandler = this.favoriteCreateHandler.bind(this);
        this.favoriteDeleteHandler = this.favoriteDeleteHandler.bind(this);
    }

}

};

The DataProviderApiFavourites has a property "create" that accepts a JSON payload, it will trigger a Redux action caught by Saga ( who will call an API ) and it also is able to notify the result through createListener function. It does the same with the delete prop ( but it will call the delete API and will notify through favoriteDeleteHandler ).

Considering that the FavouriteComponent is a stateless component, is it a good structure in order to use functionality from data components in UI components?

Actually, the DataProviderApiFavourites could be used without nesting the child component, it returns null or this.props.children if is set.

I'll really appreciate if someone will help me to understand if I'm going to do something wrong.

1 Answers

Sure, HOC would fit this use case perfectly.

The code is good, however, to stick to the idea of composition and reusability, I would build an HOC that is more generic and don't put that much logic in it, then if you need something more specific, pass arguments or build another HOC on top of it.

ex:

const HOC = store => WrappedComponent => props => (
    // Render your wrapped component, spread the store to his props
);

Where you would pass the store as an argument to the HOC and then if you need a specific slice of it, either specify it as the first argument or use another HOC abstraction.

In short, instead of using an HOC that will directly accept your wrapped component, make another HOC that will accept a first argument, it will give you more flexibility. This first argument could be the redux store, destructured redux store, the default state, etc depending on the logic you choose to go with.

  • Alternatively, recompose might also work for what you want.
  • Again, alternatively, you can make an HOC for redux's connect, have a look at this answer on SO
Related