Real-Time Charts with Meteor, React and D3

August 13, 201514 min read

Last reviewed December 27, 2018

Meteor has recently announced official support for React as its rendering engine (together with Blaze). This is great news, once React is becoming more and more popular and brings improvements in many aspects of your code (reusability, unit-testing, no spaghetti code…).

Since the React fever, many tutorials explain how to integrate it with the most popular libraries outside. The perspective of a reactive chart using React components and D3.js is pretty exciting and in fact there are many guides out there exploring this subject. This one correlates D3 and React lifecycles and this other explores svg components.

The aim of this tutorial is to take a step further and show how to build reactive charts with React and D3, but using the Meteor ecosystem.

If you are not familiar at all with React and D3 I suggest you to take a break and read about them before continuing.

In order to make things nicer, we will develop a full-functional web application that will keep track of how many beers you drink on each day of the week. More than detecting if you are an alcoholic, the aim of this app is to show how we can take advantage of React power to build reactive components that comprises D3 elements and receive data from Meteor.

You might want to follow the code in github. Time to start!

Designing the App

As React is component-based this step is important and may save you a lot of time in the future. The plan is to have two columns:

  • The leftmost will have a form to insert data and a list of all data previously inserted when one will be able to remove it by clicking.
  • The rightmost will render a bar chart.

Let’s breakdown our app into components:

Dashboard preview

The App component encompasses all components and will be responsible for gathering data from the MongoDB and passing it accordingly.

The black rectangle is the BeerForm component. It’s a basic form where we will add the number of beers and the day that event happened.

The red rectangle is the BeerList component, which encapsulates the data and renders a BeerItem component (in pink), formatting the data nicely and allowing us to click in any record to delete it.

Finally, the blue rectangle is our star – the BarChart component. It will receive data from Meteor and render this nice bar chart. This component was designed to be reusable, so the only thing you will need to do is to pass the correspondent data and it will be able to render the chart (with an horizontal label and a top label informing quantity).

Now time to dig into some code!

Setting up the environment

This is the default step. In your console, type:

$ meteor create beer-dashboard
$ cd beer-dashboard

You will note that three files were automatically generated. Get rid of them. Time for adding packages:

$ meteor add react
$ meteor add d3js:d3
$ meteor add momentjs:moment
$ meteor add twbs:bootstrap
$ meteor add fortawesome:fontawesome

The first is the official react package bundled by the Meteor Development Group. The second is the official D3.js package for Meteor. We are doing some date manipulation, so MomentJS is a good choice. Fourth and fifth are Bootstrap and FontAwesome, just for styling purposes.

Defining the Collection

Let’s begin defining the only server-side piece of our app (although it is also client-side in order to achieve latency compensation). In the top-level directory of your app, create a collections.js file and add the following:

Beers = new Meteor.Collection("Beers");

Meteor.methods({
	"insertBeer": function(numBeers, date) {
		numBeers = parseInt(numBeers);		

		check(numBeers, Number);
		check(date, Date);

		return Beers.insert({beers: numBeers, date: date});
	},

	"removeBeer": function(id) {		
		check(id, String);		
		return Beers.remove(id);
	}
})

We are defining our collection (Beers) and two methods which will be called in our components. Here, insertBeers takes the number of beers and the date, checks for inconsistencies and saves into the DB. On the other hand, removeBeer takes an id and removes the entry.

BeerForm Component

This is the top panel of the left-side column and should be straightforward. Create a client/ folder and beerform.jsx inside it. Then add the code:

BeerForm = React.createClass({
	handleSubmit: function(e) {
		e.preventDefault();
		var numBeers = React.findDOMNode(this.refs.numBeers);
		var beerDate = React.findDOMNode(this.refs.beerDate);
		
		Meteor.call("insertBeer", numBeers.value, moment(beerDate.value).toDate(), function(e, r) {
			if (e) alert(e.reason)			
		});

		numBeers.value = "";
		beerDate.value = "";
	},

	render: function() {		
		return (
			<div className="panel panel-default">
			  <div className="panel-heading">
			    <h3 className="panel-title">Beer Consumption</h3>
			  </div>
			  <div className="panel-body">
			    <form className="form-horizontal" onSubmit={this.handleSubmit}>
				  <div className="form-group">				    
				    <div className="col-sm-10">
				      <input type="number" className="form-control" 
				      		placeholder="How many beers?" ref="numBeers" />
				    </div>
				  </div>
				  <div className="form-group">				    
				    <div className="col-sm-10">
				      <input type="date" className="form-control" ref="beerDate"/>
				    </div>
				  </div>
				  
				  <div className="form-group">
				    <div className="col-sm-10">
				      <button type="submit" className="btn btn-primary btn-block">Add</button>
				    </div>
				  </div>
				</form>
			  </div>
			</div>
		);
	}
})

If you are familiar with React, there’s no news in there. For those that are not so much, let me explain a little bit.

React.createClass() is where you create your components. The only mandatory function every component must have is the render function, the place you will actually render something into the DOM.

From line 17-42 is just Bootstrap markup. Two important points:

When writing JSX files (which React will take care of translating to pure JavaScript for you) you write className instead of class. Line 22 handles the submit calling the handleSubmit() function defined previously. From line 2-13, our custom function handleSubmit() will take care of any event it was attached (in this case, onSubmit as you can guess). Notes on this code:

Line 3 we prevent the form to be actually sent. Lines 4-5 get the DOM nodes of the fields through the ref attribute, passing in Lines 7-9 their correspondent value to the Meteor method we defined before. Note this call is asynchronous. Lines 11-12 we just clean the form up. Great! Our first component is done and we are able to insert data into our collection. Go to your browser console and type Beers.find().fetch() to see if it’s working. The only missing point is we haven’t defined where BeerForm will be rendered.

Meteor-powered React

It’s time for starting sketching our App component – which will render all components together. But before that we need to render the component, so create the files client/index.html and client/init.jsx and put the following:

<head>
  <title>React + d3 + Meteor</title>  
</head>

<body></body>
Meteor.startup(function() {
	React.render(<App />,  document.body);	
});

Here we are instructing React to render the App component into document.body when client starts.

Now create client/app.jsx:

App = React.createClass({
	mixins: [ReactMeteorData],

	getMeteorData() {
	    return {
	      beers: Beers.find({}).fetch()
	    }
  	},

	render: function() {		
		return (
		  <div>
		  	<div className="page-header">
					<center>
						<h1>
						<i className="fa fa-beer"></i> Beer 
						<small> Dashboard</small></h1>
					</center>
			</div>

			<div className="container">
				<div className="row">
					<div className="col-md-4">
						<BeerForm />		
					</div>

					<div className="col-md-offset-2 col-md-6">					
					</div>
				</div>
			</div>        
		  </div>
		);
	}
});

Try to run your code now to see if everything is working. It should display the BeerForm component and the header. The only tricky thing here is Line 2-8. There is where Meteor shines and really empowers React.

While in Line 2 you tell React you are going to fetch data from your Meteor database, the function getMeteorData() says you are storing the object beers in this.data. Better still, it’s reactive! You now can pass it via props to children components, so you can add that to your code:

...
<div className="row">
  <div className="col-md-4">
    <BeerForm />
    <BeerList data={this.data.beers}/>
  </div>