UPDATE: A new version of the Complete React on Rails Course is open for pre-orders. Get $10 Off Today.
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
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