Part 2: Stop writing mapStateToProps, start using declarative models

Kyle Ramirez
6 min readNov 19, 2017

Please also read Part 1 of this story, where I talk about the background, and how we arrived at Reactive Record.

My goal here is to show you how you can connect your single-page application, which uses React and Redux, to any API backend which responds to GET / POST / PUT / DELETE requests. I’ve used this same pattern with Ruby on Rails, Sinatra, Express, Wordpress and CouchDB. It works, and it’s going to seriously speed up your workflow.

I’m going to structure this as an introduction tutorial, though this won’t be a guide for how to write AJAX requests in React when using a Ruby on Rails backend. This is sort of a game changer, and will require a short amount of learning before you’re ready to use it for fun and profit.

Reactive Record is a project started at Avail, an online service for smart DIY landlords who want to automate their landlord business. We deal with dozens of models, with dozens of fields, and dozens of unique API requests every page load. Our landlords love our software. It works, and we do things on a daily basis to make it more and more bulletproof (as in people can’t break it). When you have a large feature offering, it becomes difficult to organize all of them where users enter the correct information, and don’t get stuck because of one oversight on your part. We learned a ton of lessons rebuilding our page-refreshing app to be a single-page application. Our site experience is A LOT better. We’re growing fast. Using Reactive Record, we’re able to iterate on and deploy new features immensely faster. A project that previously took experienced engineers 5 months, took us 3 weeks. That is not an exaggeration. I’ve bundled these lessons learned into one single package, called Reactive Record, which will totally transform the way you handle data on your front-end, and let you pump out features faster for your users.

What is Reactive Record?
All on its own, Reactive Record is a front-end-only Object Data-store Mapping (ODM) implementation that lets you interact with RESTful APIs. By defining models on the front end and integrating closely with the popular state container, Redux, Reactive Record allows you to retrieve and manipulate data from your backend, and use it in an intuitive way in React. Agnostic to back-end architecture, it’s built for APIs which respond to GET/POST/POST/DELETE requests for resources identified by predefined keys, so Rails, Express, Sinatra, CouchDB, etc. Reactive Record allows you to write syntax like this:

const userAddress = new Address

userAddress.address1 = "1100 Congress Ave"
userAddress.city = "Austin"
userAddress.state = "TX"

userAddress.save()

The above syntax would make a POST request to /addresses, which should respond with a response code 201 Created, and a copy of the new record created:

REQUEST:
Method: POST
URL: /api/addresses
Data: {
"address1": "1100 Congress Ave",
"city": "Austin",
"state": "TX"
}
RESPONSE:
Status: 201 Created
Body: {
"id": 9,
"createdAt": "2016-11-20T05:21:12.988Z",
"updatedAt": "2016-11-20T05:21:12.988Z",
"userId": 2,
"address1": "1100 Congress Ave",
"city": "Austin",
"state": "TX"
}

You can interact with this resource in several ways, each method that would interact with your API would return an ES6 Promise.

userAddress.state // Returns "TX"

userAddress.updateAttributes({ state: "AK" }).then(() => {
userAddress.state // Returns "AK"
})

This is highly similar to syntax used in Active Record, for people familiar with Ruby-on-Rails. A few things happened in the background here, which are worth noting:

  • Automatic route interpolation: We named the model Address, and Reactive Record automatically pluralized that noun and generated a route for a POST request to /addresses. This assumes you already have an API backend for this route, which takes certain attributes to build an address resource. If you named the model BankAccount, by default, Reactive Record would generate an API request to /bank-accounts.
  • Automatic persistence of resource: Because we called new Address, Reactive Record automatically knew the resource did not exist on the backend. Basically, it’s never heard of it, so it knew the first save of this resource should be a POST request to create the resource. It then reloaded the persisted API resource in place, so now we can call methods on it, such as getting its ID. (userAddress.id).

Love it? We’re just getting started. Reactive Record comes with 3 extremely helpful React components, and one higher order component, which allow you to create, read, update and delete API resources in a declarative way. Those components are Member, Collection, Form and validated. That looks something like this for Collection:

import ReactiveRecord, { Member, Collection } from "reactiverecord"
const Post = ReactiveRecord.model("Post")

function Posts() {
return (
<Collection for={Post} where={status: "published"}>
{ posts => (
<div className="post-excerpts">
{posts.map( post => <PostExcerpt resource={post} /> )}
</div>
)}
</Collection>
)
}
/* Another example, taking an ID from the route parameters */
function FullArticle({ params:{ id } }) {
return (
<Member for={Post} find={parseInt(id)}>
{ post => (
<div className="the-content">
<h1>{post.title}</h1>
<small>Published: {post.createdAt}</small>
<div>{post.content}</div>
</div>
)}
</Member>
)
}

Upon render, the above would make a single API request to /posts?status=published and return a ReactiveRecordCollection of the posts, already synced with a Redux store, and already having all model instance methods such as reload, save, destroy, updateAttributes, updateAttribute. This is incredibly convenient. If you’re rendering a list of to-dos, you can simply delete a todo by including the destroy method in an onClick event.

A few things to keep in mind are:

  • No connect higher order component was used, and yet these resources are definitely available in your Redux store.
  • No mapStateToProps was used to map the location of these resources in your store to the component’s state. This magic happens in the Collection and Member components, and becomes available through a technique called function as children, which passes information to the immediate child of a component, and do with it what you may. This is very efficient, as it allows you to skip writing mapStateToProps over and over again. Or any boilerplate code for that matter. The only thing that matters is you get the data you asked for without writing much of anything for a query. The children of Collection and Member are rendered exactly 3 times on page load. Once before any API requests have been made, once because an API request has been made, and once because the request has either succeeded or failed. It will render again each time one of the resources in the API request changes, keeping your UI in sync with every resource that is changing whether done with the syntax in the beginning of this article or the declarative way demonstrated just above.

If this sounds too good to be true, keep reading part 3 of this series (coming soon). I’m about to show you how incredibly easy this is to set up, and get going in your own application. We’re going to go over:

  • How to basically set up Models, Collections and Members in your single page application.
  • How to create, update and delete resources using the Model methods, and then with the Form component.
  • How to validate data on the Form using traditional and custom validations, which prevent the form from being submitted until data is clean and validated.
  • How to setup a basic CRUD model (Create, Read, Update, Delete) across several pages using React Router and Reactive Record.

If you’re excited about this, please let me know in the comments. I want to know any upfront questions you have about how this works, because I make a lot of assumptions in trying to explain these concepts for the first time to strangers.

Continue reading Part 3 below:

--

--

Kyle Ramirez

Engineer at The Mom Project, pilot on airplane mode, former US Marine PAO, love building, storytelling and things that go fast