7 Javascript tricks that will help you save the world.

7 Javascript tricks that will help you save the world.

but not really!

Okay, you got me, the title is a bit misleading, this blog post will not equip you with the means needed to successfully steer the wheel away from the path to the dark predicament the world is probably at some point will eventually be put in.

At least one or two of my friends would be surprised that I'm writing an article about things I enjoy using in Javascript given that I used to be that person that memes about what's the result of {} + [] and [] + [] and things like that.

But, anyway, here are 7 tricks I like in JS:

[S, p, r, e, a, d] Operator:

Spread operator (...) can be used to expand any iterable (for example an array or an object) in a place where function arguments, array elements, or object attributes are expected [1]

An example of using it for the first place, function arguments, would be:

// 100% solid and relatable example:
function sum(x, y) {
    return x + y;
}

const arrayOfTwoNumbers = [1, 2];

console.log(sum(...arrayOfTwoNumbers));

As you may expect: ... will expand arrayOfTwoNumbers to the sum function arguments.

Using it with an array may be helpful when you're trying to "join" multiple arrays or objects returning a new one.

const a1 = [1, 2, 3]
const a2 = [3, 4, 5]

const a3 = [...a1, 'x', 'y', ...a2]

console.log(a3); // prints: [1, 2, 3, 'x', 'y', 3, 4, 5]
const obj1 = { x: 1 }
const obj2 = { y: 2 }

const combinedObject = { ...obj1, ...obj2 }
console.log(combinedObject); // prints: { x: 1, y: 2 }

It also gets handy working with objects making a shallow copy to avoid mutating them.

const originalObj = { x: 1 };

function addY(obj, y) {
   // this will mutuate `originalObj` as it's passed by reference.
   obj.y = y;
   return obj;
}

const newObjButNotReally = addY(originalObj, 10);

console.log(originalObj.y); // prints: 10

But with spread operator, we can change this to:

const originalObj = { x: 1 };

function addY(obj, y) {
   // this will not mutuate `originalObj` but creates a new object
   const shallowCopy = { ...obj, y };
   return shallowCopy;
}

const newObject = addY(originalObj, 10);

console.log(originalObj.y); // prints: undefined.

Note that, as you probably noticed, when defining an object if an attribute name is the same as the name of a variable in your context you don't need to explicitly specify the value i.e. { y: y } can be shortened to { y }

And it also makes building an object with conditional attributes far more compact, for example, here's a snippet from a recent project I worked on:

const createQuizNotificationObject = () => ({
  title: 'Ready for a Quiz? 🤓',
  body: 'Don\'t forget to take a Quiz tody!',
  ... (Platform.OS === 'ios') ? {
    sound: true,
  } : {
    sound: true,
    priority: 'high',
    sticky: false,
    vibrate: true,
  },
});

If you're not familiar with arrow functions we'll go over them real quick in a minute but the relevant bit here is based on Platform.OS I'm spreading one of two objects that makes sense for that platform notification, and the returned object would be one of two at the end:

{ 
  title: 'Ready for a Quiz? 🤓',
  body: 'Don\'t forget to take a Quiz tody!',
  sound: true
}

or

{ 
  title: 'Ready for a Quiz? 🤓',
  body: 'Don\'t forget to take a Quiz tody!',
  sound: true,
  priority: 'high',
  sticky: false,
  vibrate: true,
}

Destructuring with Values=Default:

One of the features I liked in Python was "Tuple Unpacking" and I was very relieved when a similar feature started picking up in JS; Destructuring.

In short, using destructuring you can "unpack" array elements or object attribute to distinct variables. [2]

So you've got a method in a DAO that returns a list of "Items" filtered by some field with pagination supported, it's not uncommon to see this pattern:

function itemsByUserId(userId, page, limit) {
  page = page || 0;
  limit = limit || DEFAULT_PAGE_SIZE;
  .....
}

P.S. This way of pagination is not very scalable and you should use Cursor Pagination instead.

Here's a faster way:

function itemsByUserId(userId, configs) {
 const { page=0, limit=DEFAULT_PAGE_SIZE, ...otherConfigs } = configs;
}

// And Invoked like:
itemsByUserId(userId, {  page: 1, limit: 10, include: 'user' })

So what's going on there? Well, first we are "destructuring" (picking) page, limit attributes from the configs object into constants with the same name.

We are also using = to shorthand assign a default value if this attribute doesn't exist on the object, and (optionally) packing all other attributes that we didn't pick into otherConfigs so we can maybe pass them on to another function.

If you'd like to pick an attribute but rename it you can use attributeName: newAttributeName for example:

const { name: userName } = { name: 'John' };
console.log(userName); // prints 'John'

Destructuring can be used with arrays as well. for example:

const [firstElement, secondElement, ...restElements] = [1, 2, 3, 4, 5, 6];
console.log(firstElement); // prints: 1
console.log(secondElement); // prints 2
console.log(restElements); // prints [3, 4, 5, 6]

const [x, y] = [1, 2, 3];
console.log(x); // prints 1
console.log(y); // prints 2

And For the destructing an object from the function argument thingy, it's usually done in the signature itself like so:

function itemsByUserId(userId, { page=0, limit=DEFAULT_PAGE_SIZE, ...otherConfigs }) {
....
}

Evermore cleaner!

=> (Arrow functions)

Arrow functions are a compact way of defining anonymous functions.

const fn = (a) => {
  return a + 10;
}

For most parts, it's no different than the traditional function definition but there are some differences.

The two differences I saw hands-on were that the arrow function establishes context (this) based on the scope it's defined in and not the one it's invoked in like the traditional functions.

Another thing is that every time the arrow function expression is executed it returns a new function, this is relevant if you're using it with React for example.

To make it even more compact you can define it like this:

const fn = (a) => a + 10; // returns a + 10

to return a value, except with objects you have to wrap it with (), for example:

const fn = (a) => ({ newVal: a + 10)}; // returns { newVal: a + 10 }

Map, Filter, and Reduce

map, filter, and reduce gives provides a nice declarative way of filtering elements of an Iterables, mapping its values to a new value, or reducing its values to a single value in a way that makes sense, respectively.

Best described by examples, so let's dive right it:

Assume you have a list of students grades records, each follows the schema:

{
  name: string,
  firstTermGrade: number,
  secondTermGrade: number,
}

And the problem is: we need to print the names of all students who passed (got 50% or more in total).

If you haven't used Map, and filter before then you've already thought of the loop way and If you have used them before then you've probably skipped this section, but let's look into the problem using map and filter.

So it's divisible into two parts, we need to "filter" students who passed based on the sum of their grades then we need their names only, so check this out:

const students = [....]
const passedStudents = students.filter((student) => student.firstTermGrade + student.secondTermGrade >= 50)

filter here takes a function, that given each element should return either true to keep it in the new list or false to skip it.

Oh and all of them don't mutate the original iterable because they're Functional programming, should've mentioned this at the start.

Now to the second part:

const studentNames = passedStudents.map((student) => student.name))
console.log(studentNames) // prints a list of strings

map takes a function that given each element returns what the new value of this element would be in the new list. Unlike filter It doesn't skip elements, but it transforms them.

For reduce let's say you'd like to return the name of the student with the highest grade.

const studentWithHighestGrade = students.reduce((acc, student) => (acc.firstTermGrade + acc.secondTermGrade) > (student.firstTermGrade + acc.secondTermGrade) ? acc : student)

Or you'd like (for some reason) the sum of all student's grades.

const sumOfGrades = students.reduce((acc, student) => acc + student.firstTermGrade + student.secondTermGrade)

In any case, reduce applies a function (called reducer) to elements, this reducer takes an accumulative value (the value so far, having gone through n - 1 element) and the nth element and it should return what the new accumulative value should be, whether it's the sum, max, min, etc. At the end reduce will return one value (the return value of the latest iteration).

One of the great things about filter, map, and reduce that you can chain them making readable and clear "pipelines" for your data, things go in one direction and magically emerges in another form, with some missing, or programmatically mashed into one artifactualized piece of data.

Keep in mind using them, that in JS when the number of elements gets large enough there's a noticeable difference in performance and in those cases, if performance is an issue then your humble for loop is the way to go.

P.S. There was a really nice article about comparing all ways to iterate in JS, can't find its link though.

?.

No, not the album by X, ?. "optional chaining" operator replaces the pattern:

const x = obj.attr1 ? obj.attr1.nestedAttr2 : undefined;

of avoiding trying to get an attribute of an undefined to:

const x = obj.attr1?.attr2

Now instead of throwing an error if the reference before ?. is nullish, it short-circuits and returns undefined

And of course, you can chain multiple of them ex: obj?.attr1?.attr2?.attr3

Promisfied setTimeout

or, in general, turning any callback function into a promise. A promise is an object representing a value that will eventually be there, you can use promises to provide ways to act on this value.... then.

Checkout this MDN Guide if you're not familiar with promises: developer.mozilla.org/en-US/docs/Web/JavaSc..

And as mentioned in the article we mostly consume promises.

But in some cases, like this one, for example, we'd like to create a promise.

Not to get into many details as I'm too scared of judgments on the mistakes I'd make doing that, promises is still an object that you can create using new.

It accepts a function that takes two functions resolve and reject. To resolve the promise you call resolve and to reject it you call.... well.... reject.

So in this example:

const sleep = (milliseconds) => {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, milliseconds);
  });
};

We've passed the resolve function as a callback to setTimeout when called the promise will resolve and now you can do something like:

sleep().then(....)

// or 

await sleep()

instead of going the AC/DC way into the callback hell.

Dynamic [propertyName]:

When defining objects you can this syntax to have a variable value as the name of an object attribute. Yes, I know this sentence is probably not very helpful, here's an example:

const key = 'name';
const obj = { [key]: 'John' }; // obj = { name: 'John '}

So here the attribute name is the value of key and not key.

It can also be used with destructuring an object if, for example, you don't know the name of the property you're interested in.

const userNameAttributeKey = 'userName';
const user = { userName: 'John' };

const { [userNameAttributeKey]: name } = user; // name = 'John';

groupBy using reduce

groupBy should provide a declarative way to group elements into an object where the key is the key you want to group by and the value is a list of elements having this value, So we'd like to:

const users = [{ name: 'John', city: 'Alexandria' }, { name: 'Doe', city: 'Cairo' }, { name: 'Jahn', city: 'Alexandria'} ]

const usersByCity = groupBy(users, 'city');

to equal

{
   Alexandria: [ { name: 'John', city: 'Alexandria' }, { name: 'Jahn', city: 'Alexandria'}],
   Cairo: [{ name: 'Doe', city: 'Cairo' }]
}

And we can establish that using reduce in this way:

const groupBy = (key, elements) => {
  return elements.reduce((acc, element) => ({
    ...acc,
    [element[key]]: [
      ...acc[element[key]] ? acc[element[key]]: [],
      element
    ]
  }), {});
}

References:

[1] developer.mozilla.org/en-US/docs/Web/JavaSc..

[2] developer.mozilla.org/en-US/docs/Web/JavaSc..