In this article I am trying to summarize a couple of techniques that are typically used for building reusable components. The idea came to me from a course on VueJS by Michael Thiessen. I am writing the examples in simple javascript functions and not in any framework code.

Video course Reusable components by Michael Thiessen is another take on some basic features of VueJS. Michael is explaining the concepts that one need to create a reusable component and specific VueJS features only happen to be the implementation.

I’m going to summarize every technique (Michael calls them levels of reusability), but I’ll only use simple javascript functions as examples, since the ideas are not framework dependant.

Templating

Is about reusing the same markup. Copy pasting a piece of markup might be a good starting point. And it might be enoughβ€”not everyting needs to be absolutely DRY and componentized.

const buttonTemplate = `<button>😊 </button>`
function MyButtonComponent() {
return `<button>😊 Foo</button>`
}

MyButtonComponent()

      const buttonTemplate = `<button>😊 </button>`
function MyButtonComponent() {
  return `<button>😊 Foo</button>`
}

MyButtonComponent()
      

Result


Configuration

Is parametrized template. This is the core of any templating language. Also this is how a so called dumb component looks like.

function MyButtonComponent({ text }) {
return `<button>😊 <em>${text}</em></button>`
}
MyButtonComponent({ text: 'My Emphasised Button' })

      function MyButtonComponent({ text }) {
  return `<button>😊 <em>${text}</em></button>`
}
MyButtonComponent({ text: 'My Emphasised Button' })
      

Result


Adaptability

A component is adaptable, when it can be used for different purposes. And it is typically the case when the component accepts slots for external content.

function MyButtonComponent(content) {
return `<button>${content}</button>`
}
MyButtonComponent('<strong>My Strong Button πŸ™</strong>')

      function MyButtonComponent(content) {
  return `<button>${content}</button>`
}
MyButtonComponent('<strong>My Strong Button πŸ™</strong>')
      

Result


Inversion

Only the child component in this example knows a special algorithm to compute something, while only the parent component knows how to present the information in text. Passing a funciton as a component property is known as render props.

// Child component
function MyButtonComponent(contentFunction) {
const computeLevelFromContent = (content) => content.length
const level = computeLevelFromContent(contentFunction(''))
const content = contentFunction(level)
return `<button>${content}</button>`
}

// Parent component
function Parent() {
return MyButtonComponent((level) => `My Powerful Button (level: ${level})`)
}
Parent()

      // Child component
function MyButtonComponent(contentFunction) {
  const computeLevelFromContent = (content) => content.length
  const level = computeLevelFromContent(contentFunction(''))
  const content = contentFunction(level)
  return `<button>${content}</button>`
}

// Parent component
function Parent() {
  return MyButtonComponent((level) => `My Powerful Button (level: ${level})`)
}
Parent()
      

Result


Extension

Extensible component is like an adaptable component in this context but with an extension point. One can provide custom content for this extension point or use the default. There might be multiple extension points, which would be implemented as named scoped slots in VueJS for example.

function MyButtonComponent({ buttonText } = {}) {
if (!buttonText) {
buttonText = (level) => `My Powerful Button (level: ${level})`
}
const computeLevelFromContent = (content) => content.length
const level = computeLevelFromContent(buttonText(''))
const content = buttonText(level)
return `<button>${content}</button>`
}

function Parent() {
return `${MyButtonComponent()} ${MyButtonComponent({
buttonText: (level) => `${level} My Extended Button πŸ˜‰`,
})}
`

}
Parent()

      function MyButtonComponent({ buttonText } = {}) {
  if (!buttonText) {
    buttonText = (level) => `My Powerful Button (level: ${level})`
  }
  const computeLevelFromContent = (content) => content.length
  const level = computeLevelFromContent(buttonText(''))
  const content = buttonText(level)
  return `<button>${content}</button>`
}

function Parent() {
  return `${MyButtonComponent()} ${MyButtonComponent({
    buttonText: (level) => `${level} My Extended Button πŸ˜‰`,
  })}`
}
Parent()
      

Result


Nesting

When we nest components while using previous techniques we end up passing content from parent components down into nested child components. Slots inside slots. Maximum adaptability but also maximum cognitive overhead.

In Vue placing a slot that is passed into another component’s slot is not that bad, but I’m not going to write a convoluted vanilla javascript example. This piece of Vue template shows a slot that a parent component can use to pass content down into AdaptableComponent and use all its props.

One interesting aspect of this is that we can decide where we want the content to come from and where in the hierarchy the default content will be placed.

<template>
<AdaptableComponent>
<template #default="scope">
<slot v-bind="scope" />
</template>
</AdaptableComponent>
</template>