Skip To Content
Back to Blog

Reusing Code in Lightning Web Components

By 08.12.21
Reading time: 4 minutes

Lightning Web Components are great for breaking down layouts and interfaces into small, reusable pieces. Still, there are situations where you may want to reuse code throughout your components: validations, string adjustments, etc. It doesn’t make sense to write the same code in multiple places – instead, we can move that code into reusable utility functions. Let’s learn how.

File and Folder Structure

Setup

My preferred method of creating Lightning Web Component utilities is via a utils folder in the standard LWC folder of a SFDX project:

The easiest way to create this folder and its files is via the SFDX: Create Lightning Web Component command. Supply utils (or your preferred name) as the component name when prompted, and delete the HTML file from the generated code (leave the metadata file in place). At this point, it can be deployed like any other component.

Despite using the Create Lightning Web Component command, our utils.js file won’t actually be a Lightning Web Component. Open utils.js and delete the boilerplate code.

Export and Import

Inside utils.js, we can define reusable functions and export them for use in other components. In the example below, I define a function called sayHello and export it:

// utils.js

export function sayHello() {
  console.log("Hello!");
}

To use sayHello in any other Lightning Web Component, import it from “c/utils” above your component’s class declaration:

// SampleLWC.js
 
import { LightningElement } from "lwc";
import { sayHello } from "c/utils";
 
export default class SampleLWC extends LightningElement {
  connectedCallback() {
    sayHello(); // Will output "Hello!" in the console on load
  }
}

Working with this

Sometimes we want to interact with the class of our component, like to reference member variables or interact with the component’s template. For example, we query for elements on the template using this.template.querySelector or this.template.querySelectorAll. This poses a problem, as this.template will not refer to the component’s template when called from the utility function.

Let’s say we have a utility function that applies some inline styles to a queried element:

// utils.js

function makeItRed(selector) {
  this.template.querySelector(selector).style = "background:red;";
}

export { makeItRed };

 

// SampleLWC.js
 
import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";
 
export default class SampleLWC extends LightningElement {
  handleClick = () => {
    makeItRed(".my-div"); // This won't work
  };
}

Calling makeItRed won’t result in any update to the Lightning Web Component because this.template will be undefined. I won’t go into the specifics of why; if you are curious to learn more about this and its various quirks, give this excellent article by Dmitri Pavlutin a read.

To fix the issue, we need to call the imported utility function and explicitly tell it to use the this from our component. We can accomplish this using the Function.prototype.call() method. The first argument of call will be set as the this value inside the function. So, we’ll just pass our current this value down:

// SampleLWC.js

import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";

export default class SampleLWC extends LightningElement {
  handleClick = () => {
    makeItRed.call(this, ".my-div");
  };
}

We can also use Function.prototype.bind() to create a bound function invocable anywhere in our component with the correct this value. This can be handy if you expect to call the function repeatedly and want to avoid writing call each time:

import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";

export default class SampleLWC extends LightningElement {
  boundMakeItRed = makeItRed.bind(this);

  handleClick = () => {
    this.boundMakeItRed(".my-div");
  }
}

For simplicity’s sake, I like to use the same name for my bound and imported functions:

import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";

export default class SampleLWC extends LightningElement {
  makeItRed = makeItRed.bind(this);

  handleClick = () => {
    this.makeItRed(".my-div");
  }
}

Either option will work.

Arrow Function Pitfall

Many devs (myself included) like to write arrow functions by default. However, arrow functions have a static context that does not change on different invocation types (as mentioned in Dmitri’s article). Therefore, the call and bind methods will not work on any utility functions defined as arrow functions.

// utils.js

// Arrow function defined with a static context
const makeItRed = (selector) => {
  this.template.querySelector(selector).style = "background:red;";
};

export { makeItRed };

 

// SampleLWC.js

import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";

export default class SampleLWC extends LightningElement {
  boundMakeItRed = makeItRed.bind(this);

  handleClick = () => {
    /*
    Neither of these will work because the utility function
    was defined as an arrow function
    */
    makeItRed.call(this, ".my-div");
    this.boundMakeItRed(".my-div");
  };
}

To resolve this, ensure that all utility functions are defined as standard functions using the function keyword.

// utils.js

function makeItRed(selector) {
  this.template.querySelector(selector).style = "background:red;";
}

export { makeItRed };

 

// SampleLWC.js

import { LightningElement } from "lwc";
import { makeItRed } from "c/utils";

export default class SampleLWC extends LightningElement {
  boundMakeItRed = makeItRed.bind(this);

  handleClick = () => {
    /*
    Now these will both work correctly
    */
    makeItRed.call(this, ".my-div");
    this.boundMakeItRed(".my-div");
  };
}

Summary

Utility classes are a great way to reuse code across your components. Functions can be defined in any basic Javascript file and imported/exported to the components that refer to them. Just make sure to use call or bind if you need to refer to this in your utility function.

Interested in bringing your Salesforce Development experience to Silverline and working with other talented developers? We’re looking for Salesforce Developers to join our team to design, develop and implement customer facing solutions on the Salesforce platform. Apply here!

We don't support Internet Explorer

Please use Chrome, Safari, Firefox, or Edge to view this site.