How to include Rails Associated Model Data in React components

When you start using React with Rails, one of the frustrating problems you quickly run into is trying to access associated model data from your React component.

Let's say we are building a chat app with Rails and React. We have three models, Chatroom, Message and User, which have the following relationships between them:

class Chatroom < ApplicationRecord
  has_many :messages, dependent: :destroy
end

class Message < ApplicationRecord
  belongs_to :chatroom
  belongs_to :user
end

class User < ApplicationRecord
  has_many :messages, dependent: :destroy
end

And we have a Message component for rendering individual messages:

import React from 'react'

const Message = ({message}) => 
  <p>
    {message.user.name}: {message.body}
  </p>

export default Message

In addition to the message body we also want to display the user's name.

If you're using a standard Rails ERB or Haml view template, you can simply write something like:

<%= @message.user.name %>

And that will just work even if you didn't include the user data in your database query in the controller.

However,  unlike in a Rails view, if we call message.user.name inside our React component without having specifically included that data in the prop sent to the component, it will throw an error. 

While a Rails template is actually able to call the model on-the-fly and get data it doesn't have, we don't have that luxury with React.

We need to explicitly compose the JSON with the associated User model data because a React component can only access the JSON data we provide it.

There are many ways to include the associated model data, including manually composing the JSON in the controller, defining a custom as_json method  on the model or using ActiveModelSerializers.

One of the cleanest and most flexible ways is to use jbuilder, which gives you a simple DSL for declaring JSON structures. The jbuilder gem comes included with Rails.

In the example above, we can include our user data in the message JSON by defining it in a _message.json.jbuilder  file like this:

json.(message, :body, :id)
json.user do
  json.extract! message.user, :id, :name, :image
end

The jbuilder DSL is quite powerful and allows you to do all sorts of custom structuring of the data.

For example, let's say we want to send all the messages in chronological order and user data for a particular chatroom to a Chatroom component. We can define the chatroom json like this:

json.(chatroom, :name, :id)
json.messages(chatroom.messages
                .sort_by{|m| m[:created_at]}) do |message|
  json.extract! message, :id, :body
  json.user do
    json.extract! message.user, :id, :name, :image
  end
end

Check out the jbuilder documentation to learn more about all its features.

The full code of the Chat app is on Github and there's a live demo running here.

You can also learn how to build it yourself in this two-part video tutorial -  How to build a chat app with Rails 5.1 ActionCable and React.js Part 1 and Part 2.