Super Awesome Index Tables for Breakfast: Griddle for ReactJS with Backbone

Marc Cyr
14 min readFeb 16, 2015

Since this was written, I actually refactored this code to make it usable application-wide — making it agnostic to where it is being used and by what.

Also, since this was written, I transitioned to a new company from where I was when this was written. If I could go back over this code, I would refactor it A LOT to make it much, much better than it is in this article. That said, it still drives home some key things for how to use Griddle.

If you are looking for an alternative to Griddle, Facebook has put out FixedDataTable. I have used it since the writing of this article, and while it is not as plug-and-play, it is much better overall (note: this is an opinion). Do yourself a favor and check it out: https://facebook.github.io/fixed-data-table/

For a quick introduction and some background on ReactJS, check out my SlideShare presentation here: http://www.slideshare.net/marcacyr/reactjs-44710845

Initially, React may throw you off as a developer because as developers, we are used to event handlers (as an example) and decoupling code to separate markup from our actual code. There is a certain comfort in seeing things like: “click .className”: “functionName”. In short, we have been taught to feel warm and fuzzy about these methods because it is a world we are increasingly growing accustomed to — the realm of bi-directional data binding and the mutable DOM.

Taking your first plunge into React, you may feel a sneaking suspicion that, as a developer, you’re not doing everything you are supposed as you are simply declaring your components and relying on a change of state to trigger this magical refresh that happens behind the scenes with the Virtual DOM.

This can take some additional getting used to if you are using libraries with pre-built components. For the purposes of this post, I’ll be talking about Griddle. Using Griddle is indeed very simple — if you are willing to load everything into the DOM at the outset of the page load and rely on Griddle to grab that data. This does not work, however, if you have a huge amount of data coming in. In that scenario, you would be better served doing something like the following: fetching data as needed by paging on the server. Please note: the approach to this does make strong use of the docs on the Griddle site for bringing in external data. I am providing this guide because those docs, as great as they are, did not bring me all the way through and I needed to do a lot to get this feature to be functional and integrate with our stack correctly.

“Griddle is a simple grid component for use with React.”the tag line borrowed from the Griddle site.

The reason is relatively straight forward: the DOM is very slow and if you shove a bunch of data into it, you will slow it down significantly.

To my point: using Griddle is very easy when you only have a few records you’re grabbing from the server and sticking in the DOM. In that scenario, you can shove everything in the DOM and it will still be efficient enough to feel as though it is lightning fast.

On the other hand, if you have a bunch of records to grab from the server, you will see significant slowdowns in the page load if you don’t utilize paging on the server. And not just paging — you should be doing your sorting, filtering, and page size changes all on the server.

The challenge falls to properly balancing these two scenarios, using Griddle in conjunction with external data.

My organization currently uses Backbone because we have used it for multiple feature build-outs in the past. Thinking back to the history of www.instagram.com, I am reminded of how they wound up with a React application. Instagram.com started in Backbone and slowly switched things over until they were left with no Backbone models. Everything was running on React using the Flux architecture, and the only remnant of Backbone was the router. Side note: see https://github.com/rackt/react-router for a React router solution.

So, with all this talk of React and Backbone, Instagram and Facebook, what’s the point? The goal is to articulate how I went about combining Backbone, React, and Griddle efficiently to build out an index page that handles paging, filtering and sorting all on the server (while rendering on the page using React so that everything loads as quickly and elegantly as possible).

My approach utilizes Backbone Paginator to allow for pageable collections. I also make use of Humps for the sake of converting camel-case into underscore (for column names), but more on that shortly.

Stack:

We’re running Ruby on Rails 3.2 with a MySQL database. Our server is a Linux box running Apache and Phusion Passenger.

Javascript-wise (for this feature), we are using BackboneJS with Backbone Paginator, ReactJS with React Addons, and making use of the aforementioned component library Griddle.

Okay — with that bit detailed, I can dive into what I actually did to build out a solution which seamlessly combined all these technologies together. Of course this is just one aspect of a massive project, but it is a vital part.

For the code snippet sections, I’m changing certain things to make them more general. One change that will be pretty obvious immediately is that “AppName” was changed from what it actually is in our application for proprietary reasons.

Disclaimer: At this point, I’m going to enter the part of the post that is not suited to the layperson. If you are reading beyond this point, you should have working knowledge of Backbone, or at least Javascript.

Routing:
Backbone Router →

"/index(/)":"indexRequest"indexRequest: function(){
console.log("Navigated to index.");
AppName.Controller.indexRequest();
}

Starting with the basic building blocks — the router is pretty straight forward. When the app hits the /index route, it goes to the indexRequest function, which then logs a console message letting us know it hit the right part of the app, and then calls the Backbone Controller method that I created to handle the index page request.

Backbone Controller:

indexRequest: function(){
React.unmountComponentAtNode(document.getElementById(‘header-region’));
React.unmountComponentAtNode(document.getElementById(‘main-region’));
React.render(
<div><h1>Index</h1></div>,
document.getElementById(‘header-region’)
);
React.render(
<AppName.ExternalComponent collection={new AppName.Collections.CollectionName} />,
document.getElementById(‘main-region’)
);
}

Again, pretty straightforward right? You can see how it calls React components into the UI. The function starts by unmounting whatever was in the DOM in the header and main regions. It then renders a very basic React component which is just a header with the name of what is being displayed on the index page (I changed it to “Index” for the purposes of this discussion).

The ExternalComponent is a React component that was built out in a separate file. An important thing to note at this point: passing the Backbone collection to the React component as a prop called “collection”. This will come into play later!

Griddle External Component:

Here is where we get into the nitty-gritty of ReactJS w/Griddle.

/** @jsx React.DOM */
AppName.ExternalComponent = React.createClass({
getInitialState: function(){
return {
“results”: [],
“currentPage”: 0,
“maxPages”: 0,
“externalResultsPerPage”: 10,
“externalSortColumn”: null,
“externalSortAscending”: true
};
},
componentDidMount: function(){
this.getExternalData();
},
getExternalData: function(){
$.when(this.props.collection.fetch())
.done(function(response){
this.setState({
results: response.purchaseRequests,
currentPage: response.currentPage — 1,
maxPages: response.maxPages,
externalResultsPerPage: response.perPage,
externalSortColumn: “id”,
externalSortAscending: null
});
}.bind(this));
},
externalSetPage: function(index){
var index = index + 1;
this.props.collection.getPage(index);
this.getExternalData();
},
changeSort: function(sort, sortAscending){
var order;
if (this.props.collection.state.order === 1) {
order = -1
} else {
order = 1
}
var sortKey = humps.decamelize(sort);
this.props.collection.setSorting(sortKey, order);
this.getExternalData();
},
setFilter: function(filter){
this.props.collection.state.query = filter;
this.props.collection.getPage(1);
this.getExternalData();
},
setPageSize: function(size){
this.props.collection.setPageSize(size);
this.getExternalData();
},
metaData: function(){
var LinkComponent = React.createClass({
render: function() {
url = “/app_path/show/”+this.props.rowData.id;
return <a href={url}>{this.props.data}</a>;
}
});
// I removed a bunch of stuff here -- if you need to use
// MetaData to rename columns and do other stuff in Griddle,
// you can look up what to put here in the documentation.
},
render: function(){
return <Griddle useExternal={true} externalSetPage=
{this.externalSetPage} externalChangeSort={this.changeSort}
externalSetFilter={this.setFilter} externalSetPageSize=
{this.setPageSize} externalMaxPage={this.state.maxPages}
externalCurrentPage={this.state.currentPage} results=
{this.state.results} resultsPerPage=
{this.state.externalResultsPerPage}
externalSortColumn={this.state.externalSortColumn}
externalSortAscending={this.state.externalSortAscending}
showFilter={true} showSettings={true}
columnMetadata={this.metaData()} />;
}
});

A lot is going on here, so let’s break it down one piece at a time, starting with the initial state. The initial state is exactly as it sounds — it is the default state for the React component. It is the veritable starting point and, as such, all those various state attributes are set to zero or some other basic default value.

The componentDidMount function is where things start to get intriguing. componentDidMount is built into the React component life-cycle. It allows one to write code to act upon the component after it has mounted to the DOM. As such, it is the ideal place for us to trigger our data fetching that we will feed to the component. So, inside the componentDidMount function, we call another function we have named getExternalData. We have abstracted this out to its own function because we have to make use of it in many different parts of the component we are building.

Our getExternalData function is a promise. If you are working with a front-end framework/library, you should already be familiar with JavaScript promises. If you are not, check out these links: Here, Here and Here

Remember that collection we handed over to the React component as a prop called “collection”? Here is where we get to make use of it.

In getExternalData we fetch the collection, and we do this as a promise, which once it’s done we will use the response to setState of the React component we are building out. I used the jQuery API for the promise to keep things simple:

getExternalData: function(){
$.when(this.props.collection.fetch())
.done(function(response){
this.setState({
results: response.dataObject,
currentPage: response.currentPage — 1,
maxPages: response.maxPages,
externalResultsPerPage: response.perPage,
externalSortColumn: “id”,
externalSortAscending: null
});
}.bind(this));
},

By setting the state (PSA: setState is a React function that is available for any component!), we are setting what we need to in order to trigger the DOM refresh through React.

To ensure that React is working with the right data every time it re-renders the component, we make use of the Backbone Collection (and Backbone Paginator/Pageable Collection), more on this below.

I’m going to take this moment to point out that all of the functions inside the ExternalComponent are being used inside the render function for the Griddle component, viz:

render: function(){
return <Griddle useExternal={true} externalSetPage=
{this.externalSetPage} externalChangeSort={this.changeSort}
externalSetFilter={this.setFilter} externalSetPageSize=
{this.setPageSize} externalMaxPage={this.state.maxPages}
externalCurrentPage={this.state.currentPage} results=
{this.state.results} resultsPerPage=
{this.state.externalResultsPerPage}
externalSortColumn={this.state.externalSortColumn}
externalSortAscending={this.state.externalSortAscending}
showFilter={true} showSettings={true}
columnMetadata={this.metaData()} />;
}

Next up we have externalSetPage. This is another one of those wonderful functions that does what its name says: this sets the page that should be fetched from the server.

externalSetPage: function(index){
var index = index + 1;
this.props.collection.getPage(index);
this.getExternalData();
},

This is built-in Griddle magic— index is passed to the function automatically without any extra code needed to do anything. All you have to do is call this.externalSetPage. Griddle will pass in the next or previous page (or whichever page number the user selected or clicked on). For example, if the user selects page 3, the index gets sent in as 2 (You know the deal. It’s the index in the array). Once inside the function, we add 1 to the index before we do anything because adding 1 to the index will get us the actual page the user was trying to select. We then make use of a Paginator function called getPage and we pass it the new index. This will automatically set this.props.collection.state.currentPage to the index we passed to getPage. If you need to find out more about this, check out the API documentation for Backbone Paginator.

By setting this.props.collection, we are setting things up to be sent up to our Backbone Collection correctly so that the fetch() will get things based on these UI updates. This is why we call getExternalData in the last line. The fetch will make use of the updated state of the component to fetch the right data from the server. React, knowing that the state has changed when the fetch() comes back and triggers setState, automatically re-renders the page and updates only what needs to be updated to keep the UI in sync with user input.

Next, the changeSort function. This is also a function Griddle gives us, so when the user interacts with the UI, Griddle passes this function exactly what it needs. The argument “sort” comes in as the column name that the user is sorting by, and sortAscending comes in as either true or false. Because this is being used in conjunction with Paginator (which takes in 1 or -1 to determine sort direction), I highjacked the function a bit and changed it up so that it worked for my purposes.

changeSort: function(sort, sortAscending){
var order;
if (this.props.collection.state.order === 1) {
order = -1
} else {
order = 1
}
var sortKey = humps.decamelize(sort);
this.props.collection.setSorting(sortKey, order);
this.getExternalData();
},

What I did here was check this.props.collection.state.order to determine what it was before the user interacted with the component. If the sort was 1, it will now be -1. If it was -1, it will now be 1. Before doing this, the sort always came in as descending, no matter what the state of the UI was when the user clicked —this is obviously not ideal.

Next up, I’ve made use of a library called “Humps” to decamelize the sort argument. I had to do this because we use MySQL and so our DB columns are in the format of “column_name”, but when ensconsed in the world of JS, things are being passed around as “columnName”. Accordingly, we decamelize the sort and set that as the sortKey. We then use a Paginator function again called setSorting and pass it the sortKey and order. This sets Paginator state on this.props.collection so that we can pass the right things up to the collection when it gets fetched by calling this.getExternalData().

Next we make use of setFilter. This takes in whatever the user types into the filter, and passes it into this function — again, this is one that Griddle automatically knows how to handle.

setFilter: function(filter){
this.props.collection.state.query = filter;
this.props.collection.getPage(1);
this.getExternalData();
},

Here, I set a custom state which I called “query” equal to the user input that makes up “filter”. On the next line, I call getPage(1) so that whatever results come back from the server, the user is shown the first page of the results. This is a necessity. If, say, the user is at page 37 of 38, and then types in a filter query, the user can be left looking at a page without results even if the results came back from the server! To mitigate this, simply call getPage(1) to make sure you show the user the first page of results. Then, call getExternalData. Easy peasy right? In the section dealing with the Rails side of things, I will show how the query gets handled on the backend.

Finally, setPageSize does exactly what it sounds like: it sets the page size. Here, again, we are making use of a Paginator function called setPageSize which sets the Paginator state on the collection props. Then, we call getExternalData again, and it sends things up to the collection and grabs records from the server based on the state.

setPageSize: function(size){
this.props.collection.setPageSize(size);
this.getExternalData();
},

You will notice there is a function called metaData. This is something you can do when using Griddle. It is used to set custom column names rather than using the names as they are returned from the server. This is useful when you want “id” to actually be something like “Request Number”. I also define a LinkComponent here, and this is also used inside the MetaData to set a link for certain columns within the table being rendered by Griddle.

The last thing you see in this file is rendering out the Griddle component using the aforementioned functions and state.

Backbone Collection:

AppName.Collections.CollectionName = Backbone.PageableCollection.extend({
url: ‘/api/route’
model: AppName.Models.CollectionModel
comparator: ‘column_name’
state:
firstPage: 1,
currentPage: 1,
pageSize: 10,
totalRecords: null,
order: 1,
sortKey: ‘id’,
query: null
queryParams:
currentPage: “current_page”,
pageSize: “page_size”,
query: ->
return this.state.query
parseState: (response, queryParams, state, options) ->
console.log(“inside parseState function”)
this.maxPages = response.maxPages
this.perPage = response.perPage
this.currentPage = response.currentPage
this.totalRecords = response.totalRecords
return response.collectionObjects
});

Right off the bat — this is written in CoffeeScript. In general, you should write CoffeeScript wherever you are able since it shortens the amount of code you have to write by, well, A LOT. This is just a general tip, and as always — to each their own! If you don’t like CoffeeScript, then don’t write it. Simple, eh?

First thing’s first, this is pretty straight-forward as well. We set the URL based on our API route. Declare which model the collection is based upon. Declare a comparator for the records (you can use pretty much any DB column you have available here). Typically, it’s “created_at”, but it really depends on your specific application needs.

State and queryParams are specific to Backbone Paginator — as is parseState (parse is something you have from Backbone, but parseState is specific to the Paginator library).

Using Paginator gives us a lot of flexibility to set params before sending them into the Rails controller.

Inside the queryParams, I declare a function called query that returns a custom state that I set in the React component from the previous section.

Lastly, we use the response from the server to set things that we will make use of in the ExternalComponent.

parseState: (response, queryParams, state, options) ->
console.log(“inside parseState function”)
this.maxPages = response.maxPages
this.perPage = response.perPage
this.currentPage = response.currentPage
this.totalRecords = response.totalRecords
return response.collectionObjects
});

That code should be pretty self-explanatory.

Rails Controller:

def requests
per_page = params[:page_size].to_i
current_page = params[:current_page].to_i
if params[:sort_by] != “team” && params[:sort_by] != “office”
sort_column = params[:sort_by]
elsif params[:sort_by] == “team” || params[:sort_by] == “office”
sort_column = params[:sort_by] + “_id”
end
sort_direction = params[:order]
@collection = ModelName.custom_query(params)
@total_records = @collection.length
max_pages = (@total_records/per_page + 1).to_i
@collection = @collection.
paginate(:page => current_page, :per_page =>
per_page).
order(sort_column + " " + sort_direction)
@req_obj = []
@collection.each do |coll|
coll_obj = {
# this is here because I had to create a unique object that
# could not just be taken right out of the DB
}
@req_obj << coll_obj
end
@collection = @req_obj
@response_obj = {
“maxPages” => max_pages,
“perPage” => per_page,
“currentPage” => current_page,
“totalRequests” => @total_records,
“collection” => @collection
}
respond_with @response_obj.to_json(root:false)
end

Rails Model Scope:

scope :custom_query, lambda {|param|
obj_scope = self.scoped({})
if param[:query]
q = “%#{param[:query]}%”
obj_scope =
obj_scope.where(‘col_name like ? OR id like ?’, q, q)
end
}

A lot is happening in the controller action and model scope shown here. First, we’re setting some variables that will be used in the pagination (using the will_paginate gem)and the ordering. These variables are per_page, current_page, sort_column and sort_direction.

I did have to do something special for sort_column. What is happening here is that I am making use of Team name or Office name while, on the model, the columns are actually team_id and office_id. So, if the sort_column comes in as “team” or “office” and is left unchanged, it will throw an unknown column error from MySQL. So, for these two specific scenarios, I append “_id” so that the sort will happen on the correct column. This results in something MySQL can work with, and the sort still happens exactly as the user would expect it to work.

For the collection, I use a model scope called “custom_query” and I pass it the params hash as an argument. Then, on the Rails model, I build out the custom scope to set an empty scope as the default. As a result, on initial load the scope won’t take effect if there are no query params, which is what we want.

If, however, the user has typed in a query, we will drop inside the conditional and use the query param to do a SQL LIKE match using ActiveRecord. This is through the where statement you see there — you would just replace col_name with the specific column you’ll be querying. In my code, I set the query on a description column and the id column to start with. You can add columns to this as necessary — just make sure your SQL is still performant as you add things to the query (watch out for slow queries!).

Once the collection comes back from the model scope, set pagination and sort using an order clause so that you give the collection to the JS with the right structure and information. In this example, I had to build out a custom JSON object rather than just taking things as they came back from the DB. This has to do with how I wanted to use things on the front-end — do what you need to here. If you can use something like ActiveModel::ArraySerializer out of the box, go ahead and do so. If you want to learn more about ActiveModel::ArraySerializer, check out the RailsCast here.

Lastly, I respond_with the response_object using to_json and root => false. This all has to do with sending the JSON object back in the specific way that I want to consume it on the front end.

There you have it!

The loop is basically this:

  1. Load the React component with inital state — which is set both on the component itself and on the Backbone collection.
  2. When the user interacts with the component in the DOM, set state as needed and send everything up to fetch the collection again and let React handle re-rendering the component on the page with the changes.
  3. Wash, rinse, repeat. The loop continues with each interaction from the user on the component.

--

--

Marc Cyr

Husband and father. Head of UI for Prisma Cloud at Palo Alto Networks running a large engineering team. Angel investor in Silicon Valley. https://20x.capital