Ruby on Rails - Comment realtime loading
Tags: ruby, react, rails, webdev
Published: 2022-01-30 08:37:43


The goal of this post was to create a real-time loading of comments on ruby on rails. Like in this demonstration: 

![screen-demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hrrfnxcydvyd68xsjm1.gif)
---

**Take note:**
> This is a continuation of my previous blog post here: [Create Post and Comment in Ruby on Rails](https://dev.to/jkevinbaluyot/create-post-and-comment-in-ruby-on-rails-3d8c). I highly suggest that you look at it since it will be used as a base for this one. If you wanna get the base first, you could check [this](https://github.com/jkevinbaluyot/PostAndComment/tree/main).

Let's start, shall we?

1. Install [react-rails gem](https://github.com/reactjs/react-rails) by adding this to the Gemfile: 
    ```ruby
    gem 'react-rails'
    ```

    then run bundle install.

    After installing, run these commands to your console:

    ```console
    $ bundle install
    $ rails webpacker:install         # OR (on rails version < 5.0) rake webpacker:install
 $ rails webpacker:install:react   # OR (on rails version < 5.0) rake webpacker:install:react
 $ rails generate react:install
 ```


2. We would be using jquery for our API. Install jquery using:
     ```console
     $ yarn add jquery
     ```

     Add this code to your environment.js.
     ```ruby
     const webpack = require('webpack')
     environment.plugins.prepend('Provide',
       new webpack.ProvidePlugin({
         $: 'jquery/src/jquery',
         jQuery: 'jquery/src/jquery'
       })
    )

    module.exports = environment
    ```

    Edit the newly generated application.js under 'app/javascript/packs/'.

    ```js
    // This file is automatically compiled by Webpack, along with any other files
    // present in this directory. You're encouraged to place your actual application logic in
    // a relevant structure within app/javascript and only use these pack files to reference
    // that code so it'll be compiled.

    // Support component names relative to this directory:
    var componentRequireContext = require.context("components", true);
    var ReactRailsUJS = require("react_ujs");
    ReactRailsUJS.useContext(componentRequireContext);

    require("@rails/ujs").start()
    require("jquery")
    ```
    Add the application.js to the head layout at the 'app/views/layouts/'.

    ```erb
    <%= javascript_pack_tag 'application' %>
    ```

3. Create the React Component.

    ```console
    $ rails g react:component CommentSection commentsPath:string
    ```
    This would generate the react component we will be using for the real-time loading of comments. The 'commentsPath:string' is the props that will pass the API URL to the component.

4. Install the active model serializer gem after adding this to your Gemfile.

    ```ruby
    gem 'active_model_serializers'
    ```

    Create the comment serializer by typing this to your console.

    ```console
    $ rails g serializer Comment
    ```

    Then add the text field to the comment serializer.

    ``` ruby
    class CommentSerializer < ActiveModel::Serializer
        attributes :id, :text
    end
    ```

5. Now we will create the controller we would be using for the API.

    Create the API folder first. Go to the controller folder in the rails app, then do this:
    ```console
    $  mkdir api
    ```

    Then go to the newly created folder and make the controller we would be using.

    ```console
    $ touch posts_controller.rb
    ```

    Edit posts_controller.rb with this code.

    ```ruby
    class Api::PostsController < ApplicationController
        before_action :set_post, only: [:comments]

        def comments
            render json: @post.comments, each_serializer: CommentSerializer
        end

        private

        def set_post
           @post = Post.find(params[:id])
        end
     end
     ```
     The posts#show should return an array of comments.

6. Add the API path to config/routes.rb.

    ```ruby
    Rails.application.routes.draw do
     # other routes

     namespace :api do
        resource :posts, only: [], defaults: {format: "json"} do
            member do
                get "/:id/comments" => "posts#comments", as: "comments"
            end
        end
     end
    end
    ``` 
    Get the pathname of the newly added route by checking 'rails routes' to your console terminal. In my case, its 'comments_api_posts_path'.

7. Add react component to post#show view. Pass the new pathname we just created in the react component.

    ```erb
    <!--app/views/posts/show.html.erb-->
    <p id="notice"><%= notice %></p>

    <%= @post.title %>
    <br>
    <%= @post.text %>
    <br>

    <b>Comments</b>
    <br>

    <%= react_component("CommentSection", { commentsPath: comments_api_posts_path(id: @post.id)}) %>

    <%= render "comments/form", comment: @comment, post_id: @post.id%>

    <%= link_to 'Edit', edit_post_path(@post) %> |
    <%= link_to 'Back', posts_path %>
    ```

    The commentsPath will be passed down the path as props in the react component.  

8. Update the React component CommentSection.js.
    ```jsx
    import React from "react"
    import PropTypes from "prop-types"

    class CommentSection extends React.Component {
       constructor(props){
          super(props);

          this.state = {
              comments: []
          }
       }
   
       componentDidMount(){
           //Run fetchComments() for the first time
           this.fetchComments();
     
           //Set Interval for running fetchComments()
           this.interval = setInterval(() =>{
               this.fetchComments();
           }, 1000);
       }
   
       componentWillUnmount(){
           // Clear the interval right before component unmount
           clearInterval(this.interval);
       }

       // Fetches Comments
       fetchComments(){

           $.ajax({
               url: this.props.commentsPath,
               dataType: 'json',
               success: function (result){
                   //Set state based on result
                   this.setState({comments: result})
              }.bind(this)
           });
       }

       render () {
           return (
               <React.Fragment>
                   <ul>
                       {
                         this.state.comments.map(function(comment, index){

                         return <li key={index}>{comment.text}</li>
                       })
                       }
                  </ul>
               </React.Fragment>
           );
       }
     }

     export default CommentSection
    ```

    A little bit of explanation. The fetchComments() function, fetches the comments of the post based on the value of commentsPath props(with the value of the API path of the current post). The result of the fetch will return an array of comments and that will be set as a state, which will be rendered by the component.

9. Change the form at the 'app/views/comments/_form.html.erb' and comments_controller.rb.

     ```erb
     <!-- app/views/comments/_form.html.erb -->
     <!--  add 'local:false' in the form_with-->

     <%= form_with(model: comment, local: false) do |form| %>
     ```  
     ---
     ```ruby
     # app/controllers/comments_controller.rb
     # edit create action

     def create
         @comment = Comment.new(comment_params)
    
         if @comment.save
             respond_to do |format|
                 format.js{ render :js => "document.getElementById('comment_text').value = '';" }
             end
         end
     end
     ```
     The javascript would remove the text after you submit the comment form.

And that's the last step! Try restarting your server, and check your localhost.

If you want to check the code, go to the [Github Repository](https://github.com/jkevinbaluyot/PostAndComment/tree/realtime-react-rails).