Love it or hate it, JavaScript is the most popular programming language according to the 2020 Stack Overflow survey. Over the years the language has developed a large ecosystem of libraries, frameworks, and tools around it. When you’re just getting started, it can feel overwhelming at first by the sheer amount of “things” you need to learn to become a proficient developer.
There are some key libraries that you can learn that will massively increase your value as a JavaScript developer. You’re very likely to find at least one (if not several) of these libraries in any given project you land in, further increasing the value of learning them.
These libraries are going to be ordered by difficulty of learning, so it’s recommended to go one by one until you’ve got a good grasp on all of them. The goal of this article is to explain what these libraries do, as well as give you a basic introduction to them. We’ll start with the most ubiquitous of them all: Lodash.
Lodash
A virtual tool belt of utilities for the JavaScript developer, knowing just a few of the many functions that Lodash gives you can greatly increase your developer productivity.
Lodash itself is a “utility” library, meaning its only goal is to do exactly that: give you a wide set of little utilities that can help you accomplish mundane tasks such as partitioning an array, memoizing functions, or unescaping HTML entities in a string.
By no means do you need to memorize the complete list of all the Lodash functions. A simpler approach would be to look through their list of documented functions, noting useful or interesting ones as you go.
Over time, you’ll encounter the same problems over and over again, and can easily reach for Lodash to help you out!
The library itself started as a fork of the library Underscore, and the two share much in common. There’s a very good chance that the next company you work for will be using one or the other for just about every project.
Here are some useful/interesting Lodash functions I’ve found myself reaching for time and time again:
partition()— Breaks an array into two separate arrays based on a predicate function in O(n) time.compact()— Removes any kind of falsey value from a given array.groupBy()— Groups a collection into a leaf plot-like object structure based on the given grouping function.shuffle()— Scrambles the indices of an array into a random order.sortBy()— Sorts an array by the given predicate input (more useful than the built-in.sort()as it returns a new array and accepts multiple, simpler inputs).curry()/curryRight()— Curries a function, enabling partial application of function parameters.debounce()— Debounces a function’s execution based on the defined wait time.memoize()— Memoizes the given function; can also take a resolver for more complex memoization when dealing with arrays or objects.cloneDeep()— Deeply clones an object.isNil()— Returnstrueif the given input is eithernullorundefined.round()— Rounds a number to the given precision.chain()— Wraps a value with a special Lodash wrapper object, allowing you to chain any Lodash function as a transformation (end the chain with.value()to get the final result).unescape()— Converts HTML entities into the actual unicode characters those entities represent.get()— Safely performs a deep traversal of an object based on the given path (if you’re working in TypeScript or using ES2020+, prefer optional chaining over this).
Get started with Lodash here: Lodash Documentation.
Immutable.js
JavaScript has seen a trend toward a more functional programming style thanks to the widespread adoption of React in recent years. Part of functional programming is making use of immutable data structures. However, many of the built-in functions for arrays are self-mutating, meaning they change the array in-place (such as .sort() or .splice()).
Immutable.js seeks to solve this problem by providing powerful immutable data structures such as Lists and Maps. It makes converting between its immutable objects and native JS objects and arrays straightforward. It also provides far more transformation and utility APIs for manipulating these structures than native JavaScript provides.
List
A List is essentially the immutable version of an array: an ordered (indexed) sequential collection of items.
const emptyList = List();const myList = List([1, 2, 3]);Transformation methods like filter() and map() work just as you’d expect:
const onlyEvens = List([1, 2, 3, 4, 5]).filter(n => n % 2 === 0);const doubledNums = List([1, 2, 3, 4, 5]).map(n => n * 2);Lists also have other useful APIs such as interleaving or subset checking that make working with them incredibly effortless. The full documentation for Lists can be found here.
Map and OrderedMap
A Map is similar to an object — an unordered collection of key-value pairs. OrderedMap is the same, except keys are maintained in a specific order and are sortable.
const emptyMap = Map();const myMap = Map({ hello: 'world' });The structure provides basic manipulation such as retrieval, deletion, and merging:
const majorCities = Map({ germany: 'Berlin', kenya: 'Nairobi', russia: 'Moscow', colorado: 'Denver', japan: 'Tokyo',});
majorCities.get('germany');majorCities.delete('berlin');majorCities.merge({ brazil: 'Rio de Janeiro', finland: 'Helsinki', norway: 'Oslo',});The key thing to remember is that this structure is immutable, so methods like .delete() or .merge() return a new map object rather than modifying the original.
The full documentation for Maps can be found here, and for OrderedMaps here.
Set and OrderedSet
Sets are collections of unique values (with the ordered version being exactly that: an ordered collection of unique values).
const emptySet = Set();const nums = Set([1, 1, 2, 2, 3, 3]);nums.size would be 3, since the only unique values are 1, 2, and 3. A common trick is creating an OrderedSet from an existing List to prune any duplicate items.
The full documentation for Sets can be found here, and for OrderedSets here.
Immutable.js has more than just Lists, Maps, and Sets — it also includes Records, Stacks, and Seqs, among others.
Get started with Immutable.js here: Immutable.js Documentation.
RxJS
RxJS is all about streams of data and performing composable transformations on those streams. A simplified way to think about this library:
“Think of RxJS as Lodash for events.” — RxJS Documentation
RxJS makes working with complicated asynchronous changes over time much easier.
Observables
The core entity is the Observable. Think of it somewhat like an array of values, except the values can be separated by time. This is where we get the idea of a “stream” — a stream of data or values over time.
The from() function takes an array and returns it as an observable. Once you have an observable, you subscribe to it:
const nums$ = from([1, 2, 3]);
nums$.subscribe(value => { console.log(value);});The dollar sign after nums$ is a convention used in the RxJS community to signify that a variable holds a reference to an observable.
Here’s a more useful example using fromEvent() to create an observable from a DOM event listener:
const clicks$ = fromEvent(document.querySelector('#myBtn'), 'click');
let clickCount = 0;clicks$.subscribe(clickEvent => { clickCount++;});This gives us a stream of click events. But tracking state in an external variable is an imperative style. Where RxJS really shines is in its ability to apply complex transformations to observables.
Operators
Operators are special functions that apply transformations to observables. Instead of imperatively tracking click count in a variable, we can use the scan() operator to transform each click event into an incrementing number:
const clicks$ = fromEvent(document.querySelector('#myBtn'), 'click');
clicks$ .pipe( scan(accumulator => accumulator + 1, 0) ) .subscribe(clickCount => { console.log(clickCount); });Operators are applied via the .pipe() method. The scan() operator works similarly to .reduce(): it takes a function and an initial accumulator value. The function receives the current accumulator and the observable value and returns the next accumulator value.
Subscriptions
When you call .subscribe(), it returns a subscription object. Observables can be either finite or infinite. Finite observables (like our array example) automatically close the subscription when the stream completes. Infinite observables (like our click event example) require you to explicitly unsubscribe to avoid memory leaks:
const clicks$ = fromEvent(document.querySelector('#myBtn'), 'click');
const clickSubscription = clicks$.subscribe(e => { console.log(e);});
clickSubscription.unsubscribe();This is just the tip of the iceberg — RxJS has over one hundred operators available, and you can create your own as well.
If you want to go deeper, the article The Introduction to Reactive Programming you’ve been missing by André Staltz covers all the whys and hows in much more detail.
Get started with RxJS here: RxJS Documentation.
Ramda
Ramda does essentially the same thing as Lodash (providing tons of useful utility functions), but with one massive difference: Ramda utilities are all designed to be used in a functional programming style.
If you’re coming to JavaScript from a language like Haskell, Elixir, or OCaml, Ramda is going to be your best friend.
Composition
One of the core fundamentals of functional programming is composition. Consider this pure function:
const double = n => n * 2;To quadruple a number, we could call double twice:
double(double(42));We could abstract this into its own function:
const quadruple = n => double(double(n));Ramda makes this more readable with compose():
const quadruple = compose(double, double);The Two Rules of Ramda
Every function from Ramda follows two rules:
- Every function is curried.
- The data a function operates on is always the last argument.
For example, Ramda’s filter() places the data last:
const nums = [1, 2, 3];const onlyEven = filter(n => n % 2 === 0, nums);Because it’s curried, you can partially apply it:
const evenFilter = filter(n => n % 2 === 0);const onlyEven = evenFilter(nums);Combine data-last curried functions with composition and you can define clean transformation pipelines:
const transform = compose( add(2), multiply(2), subtract(5));
transform(10); // 12Here’s a more realistic example. Given this data structure:
const records = [ { title: 'Section B', entries: [5, 1, 2, 10, 7] }, { title: 'Section A', entries: [4, 1, 2, 7] },];With the goal of sorting records by title ascending, and sorting each record’s entries numerically descending, a Ramda solution looks like this:
const recordMapper = compose( map(over(lensProp('entries'), sort(descend(identity)))), sort(ascend(prop('title'))));Imagine how many lines of code the same transformation would take written imperatively. The end goal of functional programming via composition is to build bigger functions out of smaller ones.
If you want to dive deeper into using Ramda in a functional style, the article series Thinking in Ramda by Randy Coulman is a fantastic place to start.
It’s worth noting that Ramda is the least common of these four libraries you’re likely to encounter on a project. If you want a functional style without adding Ramda as a dependency, Lodash ships a submodule lodash/fp that exposes all Lodash functions in a functional programming manner.
Get started with Ramda here: Ramda Documentation.
Conclusion
Learning these libraries will make you not only a better JavaScript developer, but a better developer overall. Three out of the four libraries discussed don’t just give you a tool belt of useful functionality — they challenge the conventional imperative or object-oriented style of programming that most people first learn when picking up a language like JavaScript.
A fundamental aspect of being a good programmer is picking the right tool for the job. That’s exactly what these libraries are: a set of tools, just one of many approaches available to you to solve any given problem. And at the end of the day, that’s what we as developers do: solve problems.