Do we need Higher Order Components in Vue.js?

Bogna “bognix” Flieger
Bethink
Published in
6 min readMay 9, 2018

In my previous article I described a way how to create Higher Order Components in Vue.js. I received a lot of great feedback which inspired me to write this article.

Before starting looking for an answer for the question: Do we need Higher Order Components in Vue.js? I would like to ask another question: What problems Higher Order Components solve? I believe the answer for that question is crucial.

For me there are two main problems HOCs solve:

  • code repetition: they allow to share common functionality between different components,
  • code organization: they allow to extend a component with a functionality on the instantiation level, not on the declaration level

Vue.js provides us with two solutions to address the problem of code repetition and to improve the code organization: mixins and scoped slots. In this article I’m going to compare those two with the Higher Order Component approach I described in the previous article.

Approach 0. Higher Order Component

This post is based on the example from the Higher Order Components in Vue.js article. Below is the code of the application from the previous article for the reference.

First, the main Application component:

Second, the CommentsList and the BlogPost components:

And finally, the withSubscription Higher Order Component:

withSubscription Higher Order Component has four responsibilities:

  1. fetch the data from external data source
  2. pass fetched data to the wrapped component
  3. add the change listener to the external data source when component is mounted
  4. remove the change listener from the external data source when component is destroyed

Approach 1: Mixin

The mixin which has the same responsibilities as the withSubscription Higher Order Component looks a lot simpler than HOC

Inside handleChange method the selectData method is called, however the mixin is missing it’s implementation. In HOC approach the selectData was passed as an argument to withSubscription Higher Order Component because it’s implementation differs in BlogPost and CommentsList. When using mixin I have to implement this method inside both componens.

Below are the BlogPost and CommentsList components with the mixin attached:

App component looks a lot simpler, instead of wrapping components with a HOC I just use BlogPost and CommentsList components.

Above mixin implementation solves one of two problems the HOC solved. It allows to extract the shared functionality and avoid the code repetition between components. However, it doesn’t address the second problem. With mixin I can’t create a BlogPost or CommentsList without a functionality from the mixin. I can always pass a prop to disable mixin’s functionality, however what if I need mixin’s logic only in one BlogPost instance and I don’t want it in remaining 100 instances? I don’t want to have a logic in my component that I don’t use in most cases.

What is more, I find it hard to debug when the method implementation is inside different file from the file inside which it’s called. With mixin approach I defined selectData inside components but I called it inside the mixin. For me it contradicts the idea of Single File Components, the idea that makes Vue so cool. With HOC I also defined selectData in different file from which it was called, however I was using props to pass this method so at least I knew from where it’s coming from.

Approach 2. Mixin++

The main disadvatage of Approach 1 is that a component and a mixin are always together. I can’t create two instances of a component, one with, and one without a mixin.

Well, that isn’t completely true, in Vue I can attach a mixin to a component “on demand”, they don’t have to be bound together in all cases. Below is the example of how to do so.

Firstly, the BlogPost and CommentsList components have to be changed. I no longer want to include mixin inside them.

Mixin is gone but the selectData method has to stay. The same rules apply as in the first Approach — CommentsList#selectData method’s definition differs from BlogPost's one.

In order to use withSubscription mixin on demand I had to change App component.

Whenever I want to use a component with a mixin I call Vue.extend(%mixin%).extend(%component%) inside component’s definition.

This approach looks really promising. It solves the same problems as HOCs, I can share logic between components and I can “attach” this logic to a component only when I need to use it. Unfortunately it is not perfect. First of all, I had to declare selectData method inside BlogPost and CommentsList components even though I may not need it in most cases I can imagine that after some time I may forget that selectData method is called by a mixin and as a result I may remove it and then land in a debugging hell. What is more, the withSubscription mixin provides data to a component — fetchedData. I have to add a fallback value inside component’s data in case I want to use a component without a mixin. Without fallback I’ll get an error in console saying that I’m referencing property that is not defined on the instance.

export default {
data() {
return {
fetchedData: ''
}
},
methods: {
handleChange() {
this.fetchedData = this.selectData(...)
}
},
....
}

Approach 3. Scoped Slots

At first, it was very hard for me to get my head around scoped slots. The official documentation wasn’t very helpful. Before I start explaining how to convert HOC into Scoped Slot I’m going to shortly explain what’s the idea behind Scoped Slots.

Scoped Slots give possibility to pass properties to a component that is rendered inside the slot of different component. The way we define scoped slots is not very different from standard slots definition. Let’s imagine we have a ComponentParent that contains scoped slot inside it. What is more this component contains a computed property called someComputed . The scoped slot is defined as follows:

<slot :data="someComputed"></slot>

It looks very much alike with regular props passing between components. The tricky part is the usage of scoped slot. Let’s imagine we have another component, a ComponentChild . What we want to achieve is to render ComponentChild inside ComponentParent slot and pass the someComputed value from ComponentParent to ComponentChild .

The usage of scoped slot looks as follows

<component-parent>
<component-child :slot-scope="parentScope" :parentData="parentScope.data"/>
</component-parent>

and here’s what happens inside the example above:

  • the ComponentChild is rendered inside the slot declared in ComponentParent
  • the ComponentChild shares the scope of ComponentParent and stores the reference to this scope inside parentScope property
  • the ComponentChild reads the data property from parentScope . The value of parentScope.data is the value of computed property someComputed from ComponentParent

With this very simple scoped slots definition in place I can move on to describing how to replace the Higher Order Component with Scoped Slot.

Firstly, the CommentsList and BlogPost components — they look exactly the same as in the approach with Higher Order Component, which is great. There is no need to add any extra code inside components definitions in order to use scoped slot.

Secondly, the definition of the component with Scoped Slot, let’s call the component: WithSubscription:

The component contains all the shared functionalities:

  • it attaches change listeners when component is created and detaches them when component is destroyed
  • it updates the data on every change inside the DataSource
  • it calls selectData method which is passed as a prop and can be different in each usecase
  • it passes the fetchedData down to a component it renders

And finally the usage:

The code above is only slightly more complex than the one from the example from the beginning of this paragraph.

The BlogPost and CommentsList components are rendered inside the scoped slot. The WithSubscription component accepts as a prop the selectData method which is different for each component. Then, each component has the access to fetchedData from WithSubscription component via the scope called withSubscriptionScope .

Summary

The Scoped Slots solve the same problems as Higher Order Components. They allow to share common functionality and they allow to add this functionality to a component only in some cases.

Are they better than Higher Order Components? It’s hard to say. They are a different approach to solve the same problem.

Are they better than Higher Order Components inside Vue.js application? Probably yes. Scoped Slots solution is provided by Vue.js, HOCs on the other hand are custom implementation that may break when Vue.js internals change.

Scoped Slots solve one more problem. They prevent naming collisions. Developer has to declare the name of the scope, hence it’s easy to follow from where each prop is taken.

If you would like to learn more about the naming collision in HOCs I recommend Michael Jackson’s video: Use a Render Prop! and Chang Wang’s article: Solving the problems of Higher Order Components without throwing the baby out with the bathwater

You can find the code from this article in github repo. You can find there 4 branches, one for each approach described in the article.

--

--