React and Ruby on Rails Lessons Learned

I recently started using React in my web apps to make them more interactive. React is great for the view layer of an application, but it needs to be paired with a backend of some sort. In my web apps, I use Ruby on Rails to generate a JSON API that is consumed by my React views. In getting started with React and Ruby on Rails, I learned a few things. I'd like to share those with you today.

If you're learning React, I recommend you check out Learning React: Functional Web Development with React and Redux.

Use "className", not "class" for CSS Classes

How do I set the CSS class of a react component? This one stumped me for a while. It turns out that "class" and "for" are reserved keywords in JavaScript, so they can't be used as attributes in JSX when writing React components. Because of this, you have to use "className" and "htmlFor" in React attributes when you want to use "class" and "for" HTML attributes. A Facebook engineer even explained why React uses "className" instead of "class".

# app/assets/javascripts/components/amazon_items/history_box.jsx
class AmazonItemsHistoryBox extends React.Component {
  render() {
    return (
        <div className="history-box">
            <div>{this.props.title}</div>
            <div>{this.props.asin}</div>
        </div>
    );
  }
}

Create JSON APIs for React to Use

Since React is just the view portion of MVC architecture, you need a way to get some data into your React components. One way to do this is with a JSON API. I chose to use JBuilder to produce my API results, and I namespaced them so that I could easily update them to a higher version in the future when I make breaking changes.

Note: it's possible to just convert models to JSON and then pass those directly into your React components. However, I would not recommend that. Creating an API instead of just passing data into React views allows you to reuse your API calls in the future and expose them for use on other web apps too. It also allows you to very clearly separate your React components and the logic that produces the data you're passing to them.

# app/controllers/api/v1/amazon_items_controller.rb
class Api::V1::AmazonItemsController < ApplicationController
    def show
        @amazon_item = AmazonItem.find_by_asin(params[:asin])
        ...
    end
end
# app/views/api/v1/amazon_items/show.json.jbuilder
# @amazon_item is nil when this is called from a model
# amazon_item must be supplied when this is called from a model
@amazon_item ||= amazon_item
json.extract! @amazon_item, :asin, :title
...

Use ModelController to consume JSON APIs Server-Side

Finally, I made sure to actually consume my JSON API when rendering my React components even while rendering on the server side! I do not do an HTTP request to get the JSON data for each of my React components. Instead, I overrode the .to_json method of the object I wanted to pass to my React object with a function call that simply renders out the results of an API call. This way I can consume my API without the overhead of actually calling the API externally.

# app/controllers/model_controller.rb
class ModelController < AbstractController::Base
    include AbstractController::Rendering
    include AbstractController::Helpers
    include AbstractController::Translation
    include AbstractController::AssetPaths
    include Rails.application.routes.url_helpers
    include ActionView::Layouts

    # Make sure your controller can find views
    self.view_paths = "app/views"

    # Add the host so URL helpers work
    def default_url_options
        {host: ENV["HOST_URL"]}
    end
end
# app/models/amazon_item.rb
class AmazonItem < ActiveRecord::Base
    def to_json
        controller = ModelController.new
        rendered_string = controller.render_to_string(template: "api/v1/amazon_items/show", formats: [:json], locals: {amazon_item: self})
    end
end
# app/views/amazon_items/show.html.erb
<%= react_component("AmazonItemsHistoryBox", @amazon_item.to_json) %>

Photo by erokism