Skip to content

JavaScript Fundamentals

This guide covers essential JavaScript concepts you’ll need before diving into Terra’s framework. This section is not here to teach you how to code, but all the conventions we use at Terra so your code is consistent with the rest of the codebase.


We don’t care what declaring word you use for variables. We try to stay mostly within ES6 so we use const for almost everything and let for those times you need to assign a variable conditionally after checking some other condition or finishing a loop.

// Example of when to use 'let'
let langParam;
if (regionParam) {
langParam = getLanguageFromRegion(regionParam);
}

You’ll see a few vars in the wild, mostly in old code, feel free to change them if you’re working on that piece of code. Leave them be if you’re just passing through, though, you know, if it works…

Best practice: Default to const, use let when you need to reassign.


In keeping with the ES6 conventions, we do prefer to use arrow functions but regular declaration is totally fine.

  • Use camelCase (starting lowercase).
  • Be descriptive but don’t use super long names to facilitate readability.

Examples:

  • getUserData.js
  • formatPrice.js

All functions that receive parameters should accept them as an object.

We do this to be consistent in all our functions and to facilitate adding / removing parameters when the function is already integrated in the code. Also this makes it so the order of the parameters is irrelevant, and it’s easier to call our functions without provoking mistakes.

We’ll also always destructure our payload, either in the parenthesis or inside the function, depending mostly on the length of the payload. If we have only a couple elements, destructuring inside the parenthesis is fine. If we have a lot and some defaults, it’s probably more legible to do it inside the function.

// Destructuring in the parenthesis
const yourFunction = ({ a, b, c = "default value" }) => {
// Function body
};
// Destructuring inside the function
const yourFunction = (payload) => {
const { a, b, c = "default value", d, e, f } = payload;
// Function body
};
// ❌ DO NOT use the payload object for every parameter ❌
const yourFunction = (payload) => {
console.log(payload.a);
console.log(payload.b);
// Function body
};

We follow a general legibility rule for the use of conditionals:

  • If the conditions are lengthy or complex, use full if statements
let typeClass = "";
typeClass = "c--card-e--fourth";
if (level == "high" && !isIndex) {
typeCustomClass = "c--card-e__ft-items__hd__meta__item--fifth";
} else if (level == "critical" && !isIndex) {
typeCustomClass = "c--card-e__ft-items__hd__meta__item--sixth";
}
  • If you’re contemplating only one condition, with no else, use an if statement too
let redirectUrl;
if (redirect_link) {
redirectUrl = redirect_link?.href
? redirect_link?.href
: `${import.meta.env.PUBLIC_API_URL}${getUrl({ ...redirect_link, language })}`;
}
  • If there’s only two conditions and it’s a simple thing, like checking if something exists or not, use a ternary operator
const TitleTag = pretitle ? "h3" : "h2";
  • If you have many conditions depending on a single variable, use a switch statements
let colClass;
switch (option) {
case "large":
colClass = "f--col-10 f--col-tablets-12";
break;
case "medium":
colClass = "f--col-8 f--col-tabletl-10 f--col-tablets-12";
break;
case "small":
colClass = "f--col-6 f--col-tabletl-8 f--col-tabletm-10 f--col-tablets-12";
break;
default:
colClass = "f--col-8 f--col-tabletl-10 f--col-tablets-12";
}

One of our most used loops, when we need to operate inside the loop but we do not need the modified array back.

if (this.DOM.dateElements && webinarSeriesRegion.date) {
this.DOM.dateElements.forEach((element) => (element.innerHTML = webinarSeriesRegion.date));
} else {
this.DOM.dateWrappers.forEach((element) => element.remove());
}

Syntax is basically the same as in forEach, but it does return the modified array so we can use it later.

const items = data.map((item) => ({
...item,
_key: item._key || Math.random().toString(36).substr(2, 9),
}));

We use for of when we need to preserve the async nature of a function executed inside a loop. No other loops truly await for the async function to resolve before continuing looping.

for (const [intIndex, element] of elements.entries()) {
...
if (boostify && boostify.method == 'click') {
...
} else if (inViewport && !shouldLoadInstantly) {
// Import library if not already present
try {
await this.importLibrary(this.asset);
// Create instance of the library
this.createInstance({ element, config, modifiesHeight, method: "Viewport" });
} catch (error) {
console.error(error);
this.debug.error(`⚠️ Error loading ${this.libraryName}`, "import");
}
} else if (!inViewport && !shouldLoadInstantly) {
...
}
}

If we need to use the index of an element, we usually just use the index available in our forEach or map, but you can also use a regular for loop.

Aside from simple methods like push, split, join, etc., we mostly use:

Returns the first element that satisfies a condition. Stops searching once found.

export const getWebinarRegion = ({ webinar, userLocation }) => {
const matchedRegion = webinar.regions.find((region) => region.value === userLocation);
...
};

Returns the filtered array according to the condition.

if (this.regionParam) {
const regionsObject = this.regions.filter((region) => region.value == this.regionParam);
webinarRegion = getWebinarRegion({
webinar: { regions: regionsObject },
userLocation: this.userInformation?.userLocation,
});
}

We use classes for most of our framework needs. Each library we import uses a class as a Handler and the backbone of our framework’s functionality is done in classes. Learn more about them here


→ Learn about handlers, SWUP, and our component system in Terra’s JavaScript Workflow

Knowledge Check

Test your understanding of this section

Loading questions...