/ projects

Metric: Building a quantitative self app in Meteor and React

From March to May of this year, I built my own realtime web app for quantitative self called Metric. Here’s a write-up on what made it awesome.

Metric project on GitHub

What I learned: wire framing, prototyping, React, React-Router, Meteor, autosuggest, advanced JavaScript (and I mean this in the, not just using JQuery and instead understanding the prototype system)

My tech stack: React.js, Meteor.js, Semantic UI, esprima, gauss (helper lib for math functions), code-mirror browser JS editor, Sugar.js for dates, ReactGridLayout for dashboard

What it did: (taken from the GitHub repo)

What is {metric}

  • An app I’m using to keep track of different metrics for self-improvement in a relational manner (Health tracking is dependent on metrics relating to my Diabetes, exercise, eating habits, Happiness is related to my self-actualization, social belonging, etc.)
  • A modern take on what spreadsheets are supposed to do – take data and compute things. We use Web tech, JavaScript and libraries instead of MACROS and plugins. We use objects categorised and stored in databases rather than tables (2D arrays) to represent data.
  • An experiment, and my new life’s work.

Key points:

  • unlike Excel, we don’t do tables
  • we do smart dashboards!
  • we do metrics and records
  • metrics are dynamically computed functions written in JavaScript, a record is just a JSON object
  • it’s all built with web tech, it’s real-time using Meteor and React
  • as a result of its client-server architecture, it can be hosted and accessed from multiple clients, and the metric computation thread is separated from the UI
  • unlike Excel, we also can handle and do natural arithmetic on dates and hours/mins/seconds

What was cool about it?

The Life Metric

So basically with Metric, you would write these little pieces of JavaScript that would take data from your records (basically tables with schemas) and do anything you would normally do in a Turing complete programming language to compute results.

But the unique selling point of Metric (that I haven’t seen any other consumer app do) was that it was able to make metrics depend on the computations of other metrics, which made possible this crazy idea that I call The Life Metric.

Everything I tracked in Metric was divided into these categories, because naturally we are human and we think better with related groups than splatters of random items. This included:

  • My Health - Hours of physical exercise
  • Diabetes - Average blood glucose level
  • Have I been testing often enough?
  • Hours of sleep
  • My Diet - Fruits
  • Vegies
  • etc.
  • My personal development - Hours of reading
  • How long have I procrastinated?
  • Did I be my best today?
  • etc.

And looked like this:

As you can see, these metrics are organised into various hierarchies. Now here’s the crazy idea

  • my average blood glucose level is computed as an average of my BGL records for the past week, I try to optimise this to be between 6 and 7. This, combined with other metrics, can give me a percentage representing how well I’m doing in this area (Diabetes).
  • I also care about how much I exercise, as this is good for mood and body etc. So I log this, and this is a metric under the Health/Exercise category.
  • **Now what if I compute a new metric for my overall health, based not on records/inputted data, but on other metrics? **Such that this will change when my Diabetes metric changes (which is because of new data). It turns out this is entirely possible because we are using JS.
  • And what if I don’t just stop there, but link every top-level metric of every category into its next category, until we reach the root category, and we have computed a metric that represents the performance everything we are tracking about ourselves — The Life Metric

And so that’s what you can do with Metric.

Metric implementation

The implementation of the actual Metric computation was fun as well.

All Metrics were defined as pieces of JS that were wrapped in an anonymous function.

Metrics.runComputeFunction = function(computeFunctionCodeString) {
    var metric = null;
    try {
        var func = Function(
            'Metrics',
            'Records',
            'metric',
            computeFunctionCodeString);
        metric = new func(
            metricApi.Metrics,
            metricApi.Records,
            metric);
    } catch (ex) {
        throw new Meteor.Error("runtime-error", "Your code failed to run when we tested it: " + ex.toString(), ex.stack);
    }
    return metric;
}

What this did was it took a piece of JS like something below:

and turned it into a JS function that you could call, with the arguments Metrics and Records.

Metrics and Records were APIs you could use in your Metric to access the data of other records and metrics, and perform functions with them (such as averaging).

But it didn’t stop there. I decided to extend Metrics and Records with helper functions such as since and the Math functions of the gauss library for averaging, statistics and other stuff.

Also the in-browser JS editor did syntax errors and code highlighting. It was a nice touch.

How did recomputations and dependencies work?

This is one of my favourite parts. I used a JS parser called esprima, written in JS, to parse our JS by walking the AST and identify the calls to these helper functions and thus dependencies to other metrics and record collections in the database. Then I used a library to hook when records or metrics were upserted into the DB and recomputed their dependencies.

Issue: there was a point where if a metric depended on another metric it would engage in an infinite loop of recomputation. I could tell naively if another metric had a direct dependency on another and solve this by warning the user when they’re coding it, but unfortunately I hadn’t the time to scan the dependency graph and fix the problem properly.

Record schema editor and React.js

The interface for adding records was one of my most fine achievements with React.js. For those who don’t know, React allows you to build interfaces declaratively, and it’s the future of UI dev on the web.

One particular aspect was making it so users could input different types of data and the system’s interface would change the form element accordingly — e.g. a yes/no field would demand a toggle, a number field would a counter, a text field a textboxt etc.

I achieved this by storing the field data as regular JavaScript values, then getting the value’s type and including a different React component (a UI component made out of JSX) depending on this. And it was as beautiful as this:

for (var i = 0, field; field = this.state.fields[i]; i++) {
	var fieldName = field.fieldName;
	var fieldView = null;
	var fieldType = Util.getObjectType(field.value);
	switch(fieldType) {
		case 'string':
			fieldView = (<UI.JSONString name={fieldName} value={field.value} updateValue={this.updateField.bind(this, i)}/>);
			break;
		case 'number':
			fieldView = (<UI.JSONNumber name={fieldName} step={field.step} min={field.min} max={field.max} value={field.value} updateValue={this.updateField.bind(this, i)}/>);
			break;
		case 'boolean':
			fieldView = (<UI.JSONBoolean name={fieldName} value={field.value} updateValue={this.updateField.bind(this, i)}/>);
			break;
		case 'Date':
			fieldView = (<UI.JSONDateTime name={fieldName} value={field.value} updateValue={this.updateField.bind(this, i)}/>);
			break;
		default:
			console.log("Error: field "+"'"+fieldName+"'"+" type isn't recognised "+(typeof field.value));
	}
}

The other beautiful thing was the inter component communication was as simple as a callback – there was no other information needed for this – just updating the value in the parent component state.

The Fluid Dashboard

The dashboard is completely configurable such that you can move around different cards containing metrics. This is really helpful for me, because I want to know some things straight up front. I’m so grateful to have ReactGridLayout for this purpose, and I think the ease of the implementation is testament to the React model.

The Realtime Updates (Meteor)

Finally, we get to how the data is stored. I’ve got to say this was the most amazing thing. From someone who started with JQuery AJAX spaghetti, then rewrote writing bits of my last web app in Angular.js (shudder), and finally to React, the Meteor data model is revolutionary. Meteor’s declarative dependency declaration to server-side database cursors matches perfectly with React’s declarative view dependency to client-side state. Look at this:

startMeteorSubscriptions: function() {
    Meteor.subscribe("metrics");
    Meteor.subscribe("categories");
},
getMeteorState: function() {
    var meteorState = {
        categories: []
    };
    var categories = Categories.find().fetch();
    categories.forEach(function(category) {
        cat = {
            path: category.path,
            _id: category._id,
            metrics: []
        };
        cat.metrics = Metrics.find({
            categoryId: category._id
        }).fetch();
        if (cat.metrics.length == 0) {
            return;
        }
        meteorState.categories.push(cat);
    });
    return meteorState;
},

What does this achieve?

  • It subscribes to changes of the metrics and categories collections
  • It loads the initial state into the React component, finding metrics, grouping them into categories, then adding them.
  • Whenever a category or record changes value (for example, if a metric is recomputed), the database cursor updates this automatically and re-runs getMeteorState.

And there is literally nothing else to it. And I think that’s amazing.

React Router

Finally I needed to mention routing. I used React-Router to build the routes in this single-page application — I used this library for building UI components, and used the exact same syntax and constructs (JSX), to build the routing system. (JS is eating the world)

DefaultRoute = ReactRouter.DefaultRoute;
Route = ReactRouter.Route;
	routes = (
	<Route name="app" handler={App} path="/">
	<Route name="add-metric" path="/metrics/add" handler={AddMetric} />
	<Route name="add-record" path="/records/add" handler={AddRecord} />
	<Route name="metric-overview" path="/metrics/:id" handler={MetricOverview} />
	<Route name="records-overview" path="/records-overview/:id" handler={RecordsOverview} />
	<Route name="dashboard" path="/" handler={Dashboard} />
	<DefaultRoute handler={Dashboard} />
	</Route>
);
ReactRouter.run(routes, function (Handler) { React.render(<Handler />, document.getElementById('root')); });

The Journey

I documented all my work, from wire framing, to prototyping, on this Tumblr. It was really intrinsically satisfying that every time I completed a feature I was able to screenshot it and post it.

I originally built Metric for my uses. I loved learning about all of these different technologies and it has given me a much more nuanced view of building apps. Eventually I realised that my use for Metric had subsided, and after finding a nifty app that performed the same purpose on Product Hunt, I decided to stop working on the project.

I have a lot of dreams for what I would do with Metric. Making the UI more humanistic is #1 – it’s not that I don’t have a taste in UX, it’s just that I needed to build a prototype ASAP. I wrote a long series of notes on a GitHub issue for this reason. Here were some wireframes:

It is worth noting I continued experimenting with the idea of quantitative self, and I built a nice prototype of a Socratic-like dialogue interface in React (inspired by the film, Her).

This has been very fun! Next up I want to play with GraphQL and functional React with Om and Clojure. For now, I’m working full-time at a marvellous company and doing uni and trying to do Bitcoin consulting! Many projects = Happy Life!

Thanks for reading, I’d be happy to answer any Qs.

Liam Zebedee

Liam Zebedee

21. Software engineer, Writer. Currently: senior blockchain/full-stack engineer contracting for BlockLab NL.

Read More