DEV Community

Cover image for Building Custom Mixins in Lightning Web Components (LWC)
Luke Secomb
Luke Secomb

Posted on • Originally published at blog.lukesecomb.digital

Building Custom Mixins in Lightning Web Components (LWC)

Mixins (or Abstract subclasses) are templates for classes that allow us to write generic functionality to be reused. Given the nature of classes, it is not possible to have more than one superclass. Mixins allow us to pass the base class as a property and unlock nested inheritance.

JavaScript Mixin Usage

Before we dive into an LWC example, lets look at how we can use mixins in a plain javascript environment.

Firstly, we must create a mixin function that our classes can use. This should be a piece of generic functionality that multiple classes can utalise.

// A simple Mixin method
// adds the `windowSize()` method to classes that implement this mixin
export const WindowSizeMixin = (BaseClass) =>
  class extends BaseClass {
    get windowSize() {
      return {
        width: window.innerWidth,
        height: window.innerHeight,
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode
import { WindowSizeMixin } from './windowSizeMixin'

// Implementing the `WindowSizeMixin` gives us access to the
// `this.windowSize` getter
class Header extends WindowSizeMixin(BaseClass) {
  getWindowSize() {
    return this.windowSize
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, calling the getWindowSize() function on our Header class will return the object defined in the WindowSizeMixin.

LWC Mixin Usage

Now we have a basic understanding of how mixins work in JavaScript, lets look at how the concept applies to LWC. Using mixins in LWC is almost identical with one difference, your BaseClass should be LightningElement (or another class that has extended LightningElement).

We'll assume the same above mixin source code that we used above (WindowSizeMixin) but instead our implementation will be a LWC component.

// don't forget to import LightningElement, we need to extend it as our base class
import { LightningElement } from 'lwc'

// assume our mixin has been created as a separate LWC to be used by multiple components
import { WindowSizeMixin } from 'c/windowSizeMixin'

// LWC expects a default export of our `LightingElement` extended component
export default class Header extends WindowSizeMixin(LightningElement) {
  getWindowSize() {
    // available via the WindowSizeMixin
    return this.windowSize
  }
}
Enter fullscreen mode Exit fullscreen mode

Stacking Mixins

The beauty of mixins is that you can use more than one. This can be useful for creating smaller snippet mixins for similar functionality that is used across multiple LWCs.

Lets have a quick look at an example. We will create another mixin method.

export const DateMixin = (BaseClass) =>
  class extends BaseClass {
    // a function that returns the current date as an object
    getDate() {
      const date = new Date()
      return {
        day: date.getDate(),
        month: date.getMonth() + 1,
        year: date.getFullYear(),
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Lets update our LWC example from above to include the new DateMixin.

// don't forget to import LightningElement, we need to extend it as our base class
import { LightningElement } from 'lwc'

// assume our mixins have been created as a separate LWC to be used by multiple components
import { WindowSizeMixin } from 'c/windowSizeMixin'
import { DateMixin } from 'c/dateMixin'

// LWC expects a default export of our component
// we can stack mixins here
export default class Header extends DateMixin(
  WindowSizeMixin(LightningElement)
) {
  getWindowSize() {
    // available via the WindowSizeMixin
    return this.windowSize
  }
  getCurrentDate() {
    // available via the DateMixin
    return this.getDate()
  }
}
Enter fullscreen mode Exit fullscreen mode

Using @wire in a Custom Mixin

@wire is a service that streams immutable data to the component. This is heavily used in on platform development and a must when it comes to building dynamic components.

Lets have a look at a simple example mixin that uses the standard getRecord wire method.

import { wire } from 'lwc'
import { getRecord } from 'lightning/uiRecordApi'

export const AccountWireMixin = (BaseClass) =>
  class extends BaseClass {
    // explicitly not setting recordId so that the consuming
    // component can choose how it wishes to implement it (e.g @api, get/set)
    // recordId

    // this can be dynamically changed by the implementing class if needed
    // this array acts as a default field list
    fieldRef = ['Account.Id', 'Account.Name', 'Account.Phone']

    @wire(getRecord, {
      recordId: '$recordId',
      fields: '$fieldRef',
    })
    wiredGetRecord(response) {
      // you can choose to either set the data to a variable for you component to consume
      // or fire off a function that your consuming component can override.
      // for this example, we will call a method
      this.handleWiredAccount(response)
    }

    /**
     * This method should be overriden by the consuming component
     */
    handleWiredAccount = (response) => {
      const { error, data } = response

      if (error) {
        // TODO: handle errors
      } else if (data) {
        // TODO: handle success
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

We can update our above example and stack the AccountWireMixin on top.

// don't forget to import LightningElement, we need to extend it as our base class
import { LightningElement } from 'lwc'

// assume our mixins have been created as a separate LWC to be used by multiple components
import { WindowSizeMixin } from 'c/windowSizeMixin'
import { DateMixin } from 'c/dateMixin'
import { AccountWireMixin } from 'c/accountWireMixin'

// LWC expects a default export of our component
// we can stack mixins here
export default class Header extends AccountWireMixin(
  DateMixin(WindowSizeMixin(LightningElement))
) {
  // ... existing code

  // override the shell method defined in the AccountWireMixin
  handleWiredAccount = (response) => {
    const { error, data } = response

    if (error) {
      // TODO: handle errors
      console.error('Oh no, something went wrong ', error)
    } else if (data) {
      // TODO: handle success
      console.log('We have account data, yay ', data)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Wrapping up

In conclusion, mixins are an incredibly useful tool in LWC development. They allow us to write generic, reusable functionality that can be shared between multiple components. By stacking multiple mixins, we are able to compose complex components from smaller, focused mixins. Mixins help keep our code DRY (Don't Repeat Yourself) and maintainable.

Top comments (0)