{"id":17932,"date":"2017-07-20T12:15:39","date_gmt":"2017-07-20T09:15:39","guid":{"rendered":"https:\/\/www.webcodegeeks.com\/?p=17932"},"modified":"2017-07-19T11:53:44","modified_gmt":"2017-07-19T08:53:44","slug":"realtime-react-rails","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/","title":{"rendered":"Realtime with React and Rails"},"content":{"rendered":"<p>When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But everybody builds a chat. So next, I thought about a realtime dashboard. But then I realized I had no data in this demo app with all but one user (me). So I decided to build a realtime map application that allows you to broadcast your location to anyone you wish.<\/p>\n<p>In this article, we\u2019ll explore how to use Rails, React (via react_on_rails gem), MobX, and websockets (via ActionCable). <a href=\"https:\/\/tag-along-app.herokuapp.com\">The demo app<\/a> can be found here. The full repository <a href=\"https:\/\/github.com\/leighhalliday\/tag-along\">can be found here<\/a>.<\/p>\n<h2>Getting Started with a New Rails App<\/h2>\n<p>We\u2019ll start by generating a new Rails app using the command <code>rails new tag_along --database=postgresql --webpack<\/code>. Next we\u2019ll add the <a href=\"https:\/\/github.com\/shakacode\/react_on_rails\">react_on_rails<\/a> gem and run the following command after committing our changes: <code>rails generate react_on_rails:install<\/code>. Remember to add <code>&lt;%= javascript_pack_tag 'webpack-bundle' %&gt;<\/code> to your application layout file.<\/p>\n<p>In a previous article, I was asked why I chose <code>react_on_rails<\/code> rather than just relying on the webpacker gem that ships in the latest version of Rails. I posed that question to <a href=\"https:\/\/twitter.com\/railsonmaui\">Justin Gordon<\/a>, the creator of react_on_rails, and he said that it also uses webpacker but provides view helpers and built-in support for server-rendered React components.<\/p>\n<h2>Creating Our Models<\/h2>\n<p>For this application, we\u2019ll be working with just two models: <code>Trip<\/code>, which keeps track of a single tracking session, and <code>Checkin<\/code>, which are all of the location updates and what time they occurred.<\/p>\n<p>The trips migration looks like:<\/p>\n<pre class=\"brush:php\">class CreateTrips &lt; ActiveRecord::Migration[5.1]\r\n  def change\r\n    create_table :trips do |t|\r\n      t.string :viewer_uuid, null: false\r\n      t.string :owner_uuid, null: false\r\n      t.string :name, null: false\r\n\r\n      t.timestamps\r\n    end\r\n\r\n    add_index :trips, :viewer_uuid\r\n    add_index :trips, :owner_uuid\r\n  end\r\nend<\/pre>\n<p>The Trip model looks like:<\/p>\n<pre class=\"brush:php\">class Trip &lt; ApplicationRecord\r\n  has_many :checkins\r\n\r\n  validates :viewer_uuid, :owner_uuid, :name, presence: true\r\n\r\n  before_validation :set_uuids, on: :create\r\n\r\n  def set_uuids\r\n    self.viewer_uuid = SecureRandom.uuid\r\n    self.owner_uuid = SecureRandom.uuid\r\n  end\r\nend<\/pre>\n<p>The checkins migration looks like:<\/p>\n<pre class=\"brush:php\">class CreateCheckins &lt; ActiveRecord::Migration[5.1]\r\n  def change\r\n    create_table :checkins do |t|\r\n      t.integer :trip_id, null: false\r\n      t.decimal :lat, null: false, precision: 10, scale: 6\r\n      t.decimal :lon, null: false, precision: 10, scale: 6\r\n      t.datetime :captured_at, null: false\r\n\r\n      t.timestamps\r\n    end\r\n\r\n    add_index :checkins, :trip_id\r\n  end\r\nend<\/pre>\n<p>The Checkin model is pretty small:<\/p>\n<pre class=\"brush:php\">class Checkin &lt; ApplicationRecord\r\n  belongs_to :trip\r\n  validates :trip_id, :lat, :lon, :captured_at, presence: true\r\nend<\/pre>\n<h2>Organizing React<\/h2>\n<p>Now that we have our models and initial setup done, we\u2019ll work on organizing our React folder structure. What I\u2019ve chosen to do is to create a new \u201cbundle\u201d called <code>Trip<\/code> in the <code>client<\/code> folder with the following subfolders:<\/p>\n<ul>\n<li><strong>components<\/strong>: which can be \u201cinjected\u201d with MobX store<\/li>\n<li><strong>containers<\/strong>: where we wrap main entry-points with a MobX Provider<\/li>\n<li><strong>services<\/strong>: contains code for communicating with Rails API\/sockets<\/li>\n<li><strong>startup<\/strong>: registering our containers with ReactOnRails<\/li>\n<li><strong>stores<\/strong>: MobX store will live here<\/li>\n<\/ul>\n<p>Just remember to update the <code>client\/webpack.config.js<\/code> file in the <code>entry<\/code> section to make it aware of our new bundle: <code>'.\/app\/bundles\/Trip\/startup\/registration'<\/code>.<\/p>\n<p>We\u2019ll start in the <code>startup<\/code> folder, where there\u2019s a single file called <code>registration.jsx<\/code>. The job of this file is to \u201cregister\u201d any component we wish to render from within a Rails view.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/startup\/registration.jsx\r\nimport ReactOnRails from 'react-on-rails';\r\n\r\nimport NewTripContainer from '..\/containers\/NewTripContainer';\r\nimport ViewTripContainer from '..\/containers\/ViewTripContainer';\r\n\r\nReactOnRails.register({\r\n  NewTripContainer,\r\n  ViewTripContainer\r\n});<\/pre>\n<h2>Setting Up MobX<\/h2>\n<p>MobX is a state-management tool for React. It\u2019s an alternative to using Redux and one that I find a lot simpler to set up and use. It has an \u201cobject-oriented\u201d feel as opposed to the purely functional feel of Redux.<\/p>\n<p>We need to install the <code>mobx<\/code> and <code>mobx-react<\/code> packages. In addition to these, we\u2019ll need to install <code>babel-plugin-transform-decorators-legacy<\/code> and follow that up with adding this plugin to our <code>.babelrc<\/code> file: <code>\"plugins\": [\"transform-decorators-legacy\"]<\/code>. While not necessary, using decorators with MobX makes things a lot cleaner and easier.<\/p>\n<p>Inside of the <code>stores<\/code> folder, we\u2019ll create the <code>TripStore<\/code>, where we\u2019ll manage the state for the Trip and its Checkins. We declare which properties we\u2019ll track state changes of by adding them at the top of the class using the <code>@observable<\/code> decorator, which comes with MobX.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/stores\/TripStore.js\r\nimport { observable, action } from 'mobx';\r\nimport TripApi from '..\/services\/TripApi';\r\n\r\nclass TripStore {\r\n  @observable trip = {};\r\n  @observable checkins = [];\r\n\r\n  constructor() {\r\n    this.tripApi = new TripApi();\r\n  }\r\n}\r\n\r\nconst store = new TripStore();\r\nexport default store;<\/pre>\n<p>It\u2019s important to note how the store was exported at the bottom of the file: as an instance of the store rather than the class itself. We want a single instance of this store across the whole application.<\/p>\n<p>We\u2019ll next take a look at the <code>NewTripContainer<\/code> component. It\u2019s not really a component, though, rather just wrapping our real component in a <code>Provider<\/code> component and also passing along the params that come from our Rails view. By wrapping Provider around our component, we can \u201cinject\u201d the MobX store into any of its children.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/containers\/NewTripContainer.jsx\r\nimport React from 'react';\r\nimport { Provider } from 'mobx-react';\r\nimport TripStore from '..\/stores\/TripStore';\r\nimport NewTrip from '..\/components\/NewTrip';\r\n\r\nexport default (props, _railsContext) =&gt; {\r\n  return (\r\n    &lt;Provider TripStore={TripStore}&gt;\r\n      &lt;NewTrip {...props} \/&gt;\r\n    &lt;\/Provider&gt;\r\n  );\r\n};<\/pre>\n<p>If we look at the <code>NewTrip<\/code> component itself, not too much is going on. We are simply including two children, which handle most of the work.<\/p>\n<pre class=\"brush:php\">import React from 'react';\r\nimport TripForm from '.\/TripForm';\r\nimport TripMap from '.\/TripMap';\r\n\r\nexport default class NewTrip extends React.Component {\r\n  render() {\r\n    return (\r\n      &lt;div&gt;\r\n        &lt;TripForm \/&gt;\r\n        &lt;TripMap \/&gt;\r\n      &lt;\/div&gt;\r\n    )\r\n  }\r\n}<\/pre>\n<p>We render this in our <code>views\/trips\/new<\/code> view with the following helper provided by react_on_rails. The route and controller\/action don\u2019t do anything too special, but you can check them out by visiting the repository provided in the introduction.<\/p>\n<pre class=\"brush:php\">&lt;%= react_component('NewTripContainer', props: {}, prerender: false) %&gt;<\/pre>\n<p>!Sign up for a free Codeship Account<\/p>\n<h2>Posting to Rails<\/h2>\n<p>When the page loads, we\u2019ll ask the user to enter their name, which we then post to our Rails app. In Rails, we\u2019ll insert a new Trip into the database and respond with the model details as JSON. This part does not use websockets. If we start from the Rails perspective, our action looks like:<\/p>\n<pre class=\"brush:php\"># app\/controllers\/trips_controller.rb\r\ndef create\r\n  clean_old_trips\r\n  trip = Trip.create!(trip_params)\r\n  render json: trip.to_json\r\nend<\/pre>\n<p>If you\u2019re wondering what <code>clean_old_trips<\/code> does, its job is to keep my free Heroku database small. Also, note that error-handling is fairly (entirely) absent from this demo. We\u2019re going to assume the client sends us beautifully valid data (which is a horrible assumption).<\/p>\n<p>The <code>TripForm<\/code> component is below. This is the first component we will \u201cinject\u201d the TripStore into. What this means is that we will have a prop called <code>TripStore<\/code>, which allows us to call any action or access any of the observable properties we set up. Comments have been added inline below.<\/p>\n<pre class=\"brush:php\">import React from 'react';\r\nimport { observer, inject } from 'mobx-react';\r\n\r\n\/\/ Inject the TripStore into our component as a prop.\r\n@inject('TripStore')\r\n\/\/ Make our class \"react\" (re-render) to store changes.\r\n@observer\r\nexport default class TripForm extends React.Component {\r\n  \/\/ When user submits form, call the `createTrip` action, passing the name.\r\n  handleSubmit = (e) =&gt; {\r\n    e.preventDefault();\r\n    const name = this.nameInput.value;\r\n    this.props.TripStore.createTrip(name);\r\n  }\r\n\r\n  render() {\r\n    const {TripStore} = this.props;\r\n\r\n    \/\/ If we already have a trip in our store, display a link that can be\r\n    \/\/ shared with anyone you want to share your realtime location with.\r\n    if (TripStore.trip.name) {\r\n      const trip_url = `${window.location.protocol}\/\/${window.location.host}\/trips\/${TripStore.trip.viewer_uuid}`;\r\n\r\n      return (\r\n        &lt;section className=\"trip-form-container\"&gt;\r\n          &lt;p&gt;\r\n            Tracking &lt;strong&gt;{TripStore.trip.name}&lt;\/strong&gt;,\r\n            share this link: &lt;a href={trip_url}&gt;{trip_url}&lt;\/a&gt;\r\n          &lt;\/p&gt;\r\n        &lt;\/section&gt;\r\n      )\r\n    }\r\n\r\n    \/\/ Display the form allowing user to create a new Trip for themselves\r\n    return (\r\n      &lt;section className=\"trip-form-container\"&gt;\r\n        \r\n      &lt;\/section&gt;\r\n    )\r\n  }\r\n}<\/pre>\n<p>Inside of our TripStore, we can add the action called above. It will use the API service we set up and, following successful creation, will subscribe to realtime updates (in this case, about our own location) and at the same time will start sending realtime location info to the server.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/stores\/TripStore.js\r\n@action createTrip = (name) =&gt; {\r\n  this.tripApi.createTrip(name).\r\n    then(trip =&gt; {\r\n      \/\/ update our observable property, triggering re-render in component\r\n      this.trip = trip;\r\n      \/\/ subscribe to websocket channel for this specific \"trip\"\r\n      this.tripApi.subscribeTrip(trip.viewer_uuid, checkin =&gt; {\r\n        this.recordCheckin(checkin)\r\n      });\r\n      \/\/ send our location to server\r\n      this.postCheckin();\r\n    });\r\n}<\/pre>\n<p>Inside of our API service, our POST to the Rails server looks like:<\/p>\n<pre class=\"brush:php\">import ActionCable from 'actioncable';\r\n\r\nexport default class TripApi {\r\n  constructor() {\r\n    \/\/ for use later on in article when we talk about websockets\r\n    this.cable = ActionCable.createConsumer('\/cable');\r\n    this.subscription = false;\r\n  }\r\n\r\n  createTrip = (name) =&gt; {\r\n    return fetch('\/trips', {\r\n      method: 'post',\r\n      headers: new Headers({\r\n        'Content-Type': 'application\/json'\r\n      }),\r\n      body: JSON.stringify({\r\n        trip: {name}\r\n      })\r\n    }).\r\n    then(response =&gt; response.json());\r\n  }\r\n}<\/pre>\n<h2>ActionCable + Websockets<\/h2>\n<p>When working with ActionCable in Rails, I had to add the <code>redis<\/code> gem and update the <code>config\/cable.yml<\/code> file to point to the correct Redis servers on both development and production:<\/p>\n<pre class=\"brush:php\">development:\r\n  adapter: redis\r\n  url: redis:\/\/localhost:6379\/1\r\n  channel_prefix: tag_along_development\r\nproduction:\r\n  adapter: redis\r\n  url: &lt;%= ENV['REDIS_URL'] %&gt;\r\n  channel_prefix: tag_along_production<\/pre>\n<p>We are going to create a file called <code>trip_channel.rb<\/code> in the <code>app\/channels<\/code> folder. Each Channel in ActionCable is meant to handle a single set of logic, similar to a Controller.<\/p>\n<p>Let\u2019s talk terminology first:<\/p>\n<ul>\n<li><strong>Channel<\/strong>: :Like a Controller in ActionCable, handling communication for a single use-case.<\/li>\n<li><strong>Room<\/strong>: If you think of a chatroom, this defines which \u201croom\u201d you are interested in sending and receiving realtime communication with. In our case, it will be for a specific Trip.<\/li>\n<li><strong>Consumer<\/strong>: This is the client; in our case, the browser that will connect to the server. Consumers can both send and receive information over the websocket connection.<\/li>\n<li><strong>Subscribe<\/strong>: When the Consumer connects to the server for a specific Channel + Room.<\/li>\n<li><strong>Broadcast<\/strong>: Sending information to all subscribers of a specific Channel + Room.<\/li>\n<\/ul>\n<pre class=\"brush:php\">class TripChannel &lt; ApplicationCable::Channel\r\n  # called when user first subscribes\r\n  # we can define where their information is \"broadcast\" from\r\n  def subscribed\r\n    stream_from \"trip_#{params[:room]}\"\r\n  end\r\n\r\n  # called when a Consumer sends information to the server\r\n  def receive(data)\r\n    # find trip using owner_uuid\r\n    trip = Trip.find_by!(owner_uuid: data['owner_uuid'])\r\n\r\n    # add additional checkin\r\n    # not recording in demo to keep DB small on free Heroku\r\n    # checkin = trip.checkins.create!({\r\n    #   lat: data['lat'],\r\n    #   lon: data['lon'],\r\n    #   captured_at: Time.zone.at(data['captured_at'] \/ 1000)\r\n    # })\r\n\r\n    # broadcast checkin to subscribers\r\n    ActionCable.server.broadcast(\"trip_#{params[:room]}\", {\r\n      lat: data['lat'],\r\n      lon: data['lon'],\r\n      captured_at: data['captured_at']\r\n    })\r\n  end\r\nend<\/pre>\n<h2>Receiving Realtime Data<\/h2>\n<p>To be able to send data over websockets, we\u2019ll first need to connect to the socket and then subscribe to a Channel + Room. If you look back to where the <code>TripApi<\/code> service in JavaScript was introduced, there was a line in the <code>constructor<\/code> that looked like <code>this.cable = ActionCable.createConsumer('\/cable');<\/code>. This sets us up with a connection to the server as a consumer of this websocket.<\/p>\n<p>Next we\u2019ll look at the <code>subscribeTrip<\/code> function in our TripApi, which is called whenever we want to send\/receive information for a Channel + Room. We provide a callback function that is called once every time the server sends realtime data to us as a Consumer.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/services\/TripApi.js\r\nsubscribeTrip = (viewer_uuid, callback) =&gt; {\r\n  this.subscription = this.cable.subscriptions.create({\r\n    channel: \"TripChannel\",\r\n    room: viewer_uuid\r\n  }, {\r\n    received: callback\r\n  });\r\n}<\/pre>\n<p>This function was called inside of the <code>createTrip<\/code> action function of the TripStore:<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/stores\/TripStore.js\r\nthis.tripApi.subscribeTrip(trip.viewer_uuid, checkin =&gt; {\r\n  this.recordCheckin(checkin)\r\n});<\/pre>\n<p>In our case, the only things the server will be sending to us are \u201ccheckin\u201d details, which is why the arrow function receives a variable called <code>checkin<\/code>. It simply calls another action that pushes the checkin to an array.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/stores\/TripStore.js\r\n@action recordCheckin = (checkin) =&gt; {\r\n  this.checkins.push({\r\n    lat: parseFloat(checkin.lat),\r\n    lon: parseFloat(checkin.lon),\r\n    captured_at: parseInt(checkin.captured_at)\r\n  });\r\n  \/\/ Let's just keep last 25 checkins for performance\r\n  this.checkins = this.checkins.slice(-25);\r\n}<\/pre>\n<h2>Sending Realtime Data<\/h2>\n<p>We talked about how realtime data was received, but how did it get to the server in the first place? After subscribing to the Channel + Room, we have a <code>subscription<\/code> object. What we will do is ask for the user\u2019s location every two seconds, sending that information to the server using this subscription.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/stores\/TripStore.js\r\n@action postCheckin = () =&gt; {\r\n  \/\/ ask for location\r\n  navigator.geolocation.getCurrentPosition(position =&gt; {\r\n    \/\/ send location + owner_uuid (secret and only owner knows about it)\r\n    this.tripApi.postCheckin(\r\n      this.trip.owner_uuid,\r\n      position.coords.latitude,\r\n      position.coords.longitude,\r\n      position.timestamp\r\n    );\r\n\r\n    \/\/ 2 seconds later do the whole thing again\r\n    setTimeout(() =&gt; {\r\n      this.postCheckin();\r\n    }, 2000);\r\n  });\r\n}<\/pre>\n<p>What <code>postCheckin<\/code> does in the API is to send the information over websockets using the subscription and its <code>send<\/code> function.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/services\/TripApi.js\r\npostCheckin = (owner_uuid, lat, lon, captured_at) =&gt; {\r\n  this.subscription.send({\r\n    owner_uuid,\r\n    lat,\r\n    lon,\r\n    captured_at\r\n  });\r\n}<\/pre>\n<h2>Showing Checkin Locations on the Map<\/h2>\n<p>We\u2019ve looked at how to both send data to the server via websockets and how to receive realtime updates back. But what are we going to do with this stream of location details? We\u2019ll show them on a map! For this, we are using the <code>react-map-gl<\/code> package, which works with MapBox.<\/p>\n<pre class=\"brush:php\">\/\/ client\/app\/bundles\/Trip\/components\/TripMap.jsx\r\nimport React from 'react';\r\nimport { observer, inject } from 'mobx-react';\r\nimport MapGL, {Marker} from 'react-map-gl';\r\nimport moment from 'moment';\r\nimport MARKER_STYLE from '..\/markerStyle';\r\n\r\n\/\/ You must sign up for a free access token\r\nconst token = process.env.MapboxAccessToken;\r\n\r\n@inject('TripStore')\r\n@observer\r\nexport default class TripStore extends React.Component {\r\n  constructor() {\r\n    super();\r\n\r\n    this.state = {\r\n      viewport: {\r\n        latitude: 43.6532,\r\n        longitude: -79.3832,\r\n        \/\/ other viewport properties like width, height, zoom\r\n      },\r\n      settings: {\r\n        \/\/ settings\r\n      }\r\n    };\r\n  }\r\n\r\n  \/\/ render a Marker for each checkin location\r\n  renderMarker = (checkin) =&gt; {\r\n    return (\r\n      &lt;Marker key={checkin.captured_at} longitude={checkin.lon} latitude={checkin.lat} &gt;\r\n        &lt;div className=\"station\"&gt;\r\n          &lt;span&gt;{moment(checkin.captured_at).format('MMMM Do YYYY, h:mm:ss a')}&lt;\/span&gt;\r\n        &lt;\/div&gt;\r\n      &lt;\/Marker&gt;\r\n    );\r\n  }\r\n\r\n  \/\/ Callback sent to the Map to handle dragging \/ panning \/ zooming of map\r\n  onViewportChange = (viewport) =&gt; {\r\n    this.setState({viewport});\r\n  }\r\n\r\n  \/\/ Helper function to set the viewport to the user's last checkin location\r\n  viewport = () =&gt; {\r\n    const {TripStore} = this.props;\r\n    let latitude = 43.6532;\r\n    let longitude = -79.3832;\r\n\r\n    if (TripStore.checkins.length &gt; 0) {\r\n      const last = TripStore.checkins[TripStore.checkins.length - 1];\r\n      latitude = last.lat;\r\n      longitude = last.lon;\r\n    }\r\n\r\n    return {\r\n      ...this.state.viewport,\r\n      latitude,\r\n      longitude\r\n    };\r\n  }\r\n\r\n  render() {\r\n    const {TripStore} = this.props;\r\n    const viewport = this.viewport();\r\n\r\n    \/\/ render actual map, mapping over each `checkins` in the TripStore\r\n    return (\r\n      &lt;MapGL\r\n        {...viewport}\r\n        {...this.state.settings}\r\n        mapStyle=\"mapbox:\/\/styles\/mapbox\/dark-v9\"\r\n        onViewportChange={this.onViewportChange}\r\n        mapboxApiAccessToken={token} &gt;\r\n        &lt;style&gt;{MARKER_STYLE}&lt;\/style&gt;\r\n        { TripStore.checkins.map(this.renderMarker) }\r\n      &lt;\/MapGL&gt;\r\n    );\r\n  }\r\n}<\/pre>\n<h2>Conclusion<\/h2>\n<p>Although it was a fair amount of code, we built something pretty darn cool: a realtime location-tracking map you can share with your friends and family! We used a lot of new technologies to make it happen, including React, MobX, ActionCable, and MapBox.<\/p>\n<p>Using ActionCable in React is really no different from using it in standard JavaScript. The key is that once you make your websocket connection, you can then subscribe to a Channel + Room. From there, you are free to both send and receive information. How you decide to display that information is up to you!<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"https:\/\/blog.codeship.com\/realtime-with-react-and-rails\/\">Realtime with React and Rails<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/join-us\/wcg\/\">WCG partner<\/a> Leigh Halliday at the <a href=\"http:\/\/blog.codeship.com\/\">Codeship Blog<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But everybody builds a chat. So next, I thought about a realtime dashboard. But then I realized I had no data in this demo app with all but one user &hellip;<\/p>\n","protected":false},"author":113,"featured_media":4127,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[95,468],"class_list":["post-17932","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ruby","tag-rails","tag-react"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Realtime with React and Rails - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Realtime with React and Rails - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2017-07-20T09:15:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leigh Halliday\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@leighchalliday\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leigh Halliday\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\"},\"author\":{\"name\":\"Leigh Halliday\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b\"},\"headline\":\"Realtime with React and Rails\",\"datePublished\":\"2017-07-20T09:15:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\"},\"wordCount\":1479,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"keywords\":[\"Rails\",\"React\"],\"articleSection\":[\"Ruby\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\",\"name\":\"Realtime with React and Rails - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"datePublished\":\"2017-07-20T09:15:39+00:00\",\"description\":\"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Ruby\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/ruby\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Realtime with React and Rails\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b\",\"name\":\"Leigh Halliday\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g\",\"caption\":\"Leigh Halliday\"},\"description\":\"Leigh is a developer at theScore. He writes about Ruby, Rails, and software development on his personal site.\",\"sameAs\":[\"http:\/\/www.leighhalliday.com\/\",\"https:\/\/x.com\/leighchalliday\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/leigh-halliday\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Realtime with React and Rails - Web Code Geeks - 2026","description":"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/","og_locale":"en_US","og_type":"article","og_title":"Realtime with React and Rails - Web Code Geeks - 2026","og_description":"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But","og_url":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2017-07-20T09:15:39+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","type":"image\/jpeg"}],"author":"Leigh Halliday","twitter_card":"summary_large_image","twitter_creator":"@leighchalliday","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Leigh Halliday","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/"},"author":{"name":"Leigh Halliday","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b"},"headline":"Realtime with React and Rails","datePublished":"2017-07-20T09:15:39+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/"},"wordCount":1479,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","keywords":["Rails","React"],"articleSection":["Ruby"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/","url":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/","name":"Realtime with React and Rails - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","datePublished":"2017-07-20T09:15:39+00:00","description":"When I was thinking about creating something to showcase using ActionCable (websockets) in a Rails app with React, I first thought of building a chat. But","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/ruby\/realtime-react-rails\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Ruby","item":"https:\/\/www.webcodegeeks.com\/category\/ruby\/"},{"@type":"ListItem","position":3,"name":"Realtime with React and Rails"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b","name":"Leigh Halliday","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g","caption":"Leigh Halliday"},"description":"Leigh is a developer at theScore. He writes about Ruby, Rails, and software development on his personal site.","sameAs":["http:\/\/www.leighhalliday.com\/","https:\/\/x.com\/leighchalliday"],"url":"https:\/\/www.webcodegeeks.com\/author\/leigh-halliday\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/17932","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/113"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=17932"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/17932\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/4127"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=17932"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=17932"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=17932"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}