Simple life of your web app data

functional approaches to everyday JavaScript data tasks

To ultimately understand the way of life of your web based apps, you have to master way data flows and changes. There are many ways to do this, and little less, but still multiple, ways to do it properly. Doing it properly is only a matter of overall maintainability… I know that it will do the task specified when first released :-)

In recent years, I started a switch to doing every operation with data functionally and following immutability principles… This is to, at some point, switch to fully FRP approach, but let us keep on subject here. I will quickly just push you through concept of immutability and then we will dive into some of functional approaches to data manipulation.

Immutability

Immutable data obviously means that it can not be changed :-)… So, basically no object is changed after creation (opposing to object oriented way, where we always change the state of object — letting it permanently change what it is).

Fully immutable approach means that for every change, a new object is created, and old one can be used as a reference to a previous state. This gives us completely new set of rules and new set of possibilities, like undo, easy other history manipulations, like following changes and optimizing data flows in natural way.

If you just want to properly implement these concepts without going into crazy depth with learning bits and pieces, I recommend facebook’s immutable.js. But then, spend some time understanding it, because I do not want people just doing what they’re told…

Working with datasets

Usually 80–90 percent of data in web apps are data sets, mostly collections or multidimensional tree-like structures with repeating data structures. If you are not working with this type of data in your apps, this article is less useful for you and you can just take a look at first couple examples.

Diving into functional approaches we have to understand that there is another important concept we have to follow, that is tightly connected to immutability as well. This is the concept that requires that every function resolves every task within itself and returns fully finished product of operation it implements as its result. Function must not have any changeable state (it is stateless) and has to avoid having any side effects — meaning, there are no variables/data structures outside method affected from inside a method.

Talk is cheap - show me the code

I’ll show you series of examples utilizing standard js higher order functions for every day tasks.

Usual approach to most of these is having for, forEach or underscore/lodash each going through list and creating stuff as a new array that gets filled outside of the loop, or, even worse, changing list itself.

Data we will use in most of examples:

/**
We got array of results coming our way, here is example of form of results:
[{
    "objectID": 9131042,
    "name": "360fly - Panoramic 360° HD Video Camera - Black",
    "description": "This 360fly panoramic 360° blah, blah blah",
    "brand": "360fly",
    "categories": [
      "Cameras & Camcorders",
      "Camcorders",
      "Action Camcorders",
      "All Action Camcorders"
    ],
    "hierarchicalCategories": {
      "lvl0": "Cameras & Camcorders",
      "lvl1": "Cameras & Camcorders > Camcorders",
      "lvl2": "Cameras & Camcorders > Camcorders > Action Camcorders",
      "lvl3": "Cameras & Camcorders > Camcorders > Action Camcorders > All Action Camcorders"
    },
    "type": "Point&shoot camcrder",
    "price": 399.99,
    "price_range": "200 - 500",
    "image": "http://img.bbystatic.com/BestBuy_US/images/products/9131/9131042_rb.jpg",
    "free_shipping": true,
    "popularity": 10000
  }, ... lot more stuff in same form]
*/

Simple iteration through dataset you received preparing it for UI usage

//Here we consider dataset being array of objects, and we want to keep it an array, just return formatted stuff
var renderData = responseData.results.map((item) => {
     return {
       name: item.name,
       price: formatPrice(item.price),
       image: item.image,
       badges: getBadges(item), //adding badges links based on popularity, price, free shipping etc...
       ...
     }
});

RenderData is now array of parsed items. Using .map() gives us ability to do simple new array setup without additional side effect and need to change original data set, keeping us completely safe from bugs… Only error you can make is in method for parsing the item.

This task is usual, and highly bug prone if done in the usual way — we setup an array and then go through list of keys putting them all in their places in the array making this process require helper data structure(s) — which have to be side effects…

I’ll make this about above example data and create list of items by id.

var renderData = responseData.results.map((item) => {
         return { [item.objectID]: item };
}).reduce((a, b) => {
   return Object.assign(a,b);
});

Method passed to .reduce() receives previousValue and currentValue as first two params, so you choose how they get squashed together (current value is always result of all previous squashing, so that can be useful for you as well).

At this point we should already figure out other big advantage of this approach — we keep working on data through the series of simple “pipelines” which just do their simple and easily controlled tasks.

Finding item in data set by content of one of its values

So, now we know how data is being parsed, how it is being iterated and returned, can we use this to filter and find stuff…

Lets first look at filtering - we first want stuff picked by keyword, then prepared for UI:

var hdRenderData = responseData.results.filter((item)=>{
     return item.name.includes('HD');
  }).map((item) => {
     return {
       name: item.name,
       price: formatPrice(item.price),
       image: item.image,
       badges: getBadges(item), //adding badges links based on popularity, price, free shipping etc...
       ...
     }
});

Here we make use of .filter(), it returns an array of items for which callback returns true. Filter the last higher order function I want to use here.

But, here are some other useful tips and examples:

Easy filter by category and get just needed data

var drones = responseData.results.filter((i) => {
   return i.categories.reduce((a, b) => (a + b)).includes(Drone);
}).map((item) => item.objectID);

Here we filter through items, reducing their categories array to a string (concatenating array of strings into a string) and taking just items who’s categories contain word “Drone” in them (using .includes() on newly formed string). Then we map array we are returning to just return IDs of found components.

Handling byId lists (any kind of lists with repeating structure but not incremental number based keys)

You first need to get an array to map through:

var keyArr = Object.keys(yourDict);

When you have structure that you can iterate through, you can get to action like:

//If you want it mapped as new array structure, with key present as an ID
var result = Object.keys(yourDict).map((key) => {
        var entity = yourDict[key];
        entity.id = key;
        return entity;
});

If you want to keep it as key: value structure, you can use map -> reduce to one object pattern from above.

Keeping history of your data changes

map(), reduce() and filter() do not mutate arrays you trigger them on, but return new data, so depending on what you want to achieve, you can track the history of your data mutations as simple as pushing new version to history array, or track more complex dimensions of changes… With immutability, this comes out of the box.

It is all about your experience developing it

Ultimately, the solution will be measured by its scalability, resilience and maintainability… Everyone on the outside just cares about if it works.

Examples and discussions above should give you another option.

In my opinion, this way of approaching data mutation gives you cleaner, simpler and more bug prone code. There are no side effects, you can not create bugs that originate outside the methods you are passing to higher order functions. As addition to simplicity, these are adding verboseness to your data processing, making code more readable and understandable.

In projects I am working on, I am implementing this programming paradigm as much as possible, with ultimate goal of extending it towards FRP approaches in the backend and client side. It did wonders on our newest front end (javascript full stack) apps in Vast.com.

Comments