Will Little Subscribe

Redux, Rails, and ActionCable :: Full-stack web development "Hello World" tutorials for entrepreneurs: Part 7 of 10


This post is part 7 of a 10-part series within a series that is designed to teach full-stack web development for entrepreneurs. Here we’ll dive even further into Redux by wiring it up with ActionCable from Rails.

Now, the careful observer will note that if we open two separate browser session windows side by side and create a quote from our Redux/React app, the other window’s Redux/React app list isn’t updating (although the Stimulus list is since we set up the CableReady broadcast there already!).

In order to ensure the state of other browser sessions is updated when we add a quote, we’re going to need to wire up our Redux/React app example to listen for ActionCable updates.

First, let’s add the Redux Cablecar JavaScript package. From our terminal in our quotesapp root folder:

  • yarn add redux-cablecar

Next, in our store.js file let’s add a new line 4:

import cablecar from 'redux-cablecar';

And add cablecar to our middleware list on the new line 8. The diff will look like this:

Now in app.js we’ll add starting on line 5:

import cablecar from 'redux-cablecar';

const car = cablecar.connect(store, 'HelloQuotesChannel');

That’s all we need to add to send action functions directly from our Rails app into our Redux app.

Now let’s open up our app>controllers>api>v1>quotes_controller.rb file and add our broadcast on a new line 16 after our cable_ready broadcast:

    ActionCable.server.broadcast("hello_quotes", {
        type: "RQA::CreateQuoteSuccess", 
        response: {
          entities: {
            quotes: {
              "#{quote.id}": quote
            }
          },
          result: quote.id
        }
      }
    )

And in our app>controllers>welcome_controller.rb file, go ahead and also copy/paste the exact broadcast code above on a new line 17. This will also update our Redux/React app when we create a quote via our traditional Rails form as well.

Before we commit our changes, double check you have these 7 modifications:

Now go ahead and review your code, which should look like this commit diff, then:

  • git commit -a -m "Connect ActionCable with Redux to add created hello world quotes to listening Redux stores"
  • git push

OK, while we successfully wired up adding quotes to other clients listening in, we still need to send those clients an action when we delete a quote.

Open up your app>controllers>api>v1>quotes_controller.rb file and on a new line 38 add:

    ActionCable.server.broadcast("hello_quotes", {
        type: "RQA::DeleteQuoteSuccess", 
        response: {
          data: params[:id].to_i
        }
      }
    )    

That will work correctly from clicks from our Redux/React app. And now in app>reflexes>quotes_reflex.rb we’ll add starting on a new line 9:

    ActionCable.server.broadcast("hello_quotes", {
        type: "RQA::DeleteQuoteSuccess", 
        response: {
          data: id.to_i
        }
      }
    )  

Saving these two file changes will allow your web browser session windows to correctly remove quotes when they are clicked and deleted.

FYI: you may have noticed the following error in your browser console that cablecar is complaining about non-cablecar things being sent through our HelloQuotesChannel (i.e. when we send things from our StimulusReflex side).

This can be ignored. You can look at line 77 from the cablecar source here to see why it’s doing that. We could create a separate channel for cablecar messages to help make this error go away, but for now - since we’re still just in our learning setup part of the tutorial series - we can ignore it.

Go ahead and check your files - which should line up with this commit diff - and then:

  • git commit -a -m "remove quotes from Redux/React example clients listening in when deleted"
  • git push

While testing all aspects of your Hello World page, you may have noticed that there is an awkward user experience when you submit your basic Rails form. This is because we used “local: true” in our form parameters in our app>views>welcome>index.html.erb file, which triggered a page reload.  This was OK for our early demo, but let’s go ahead and refactor our rails code to allow that form to be submitted behind the scenes.

Open up your app>views>welcome>index.html file and change line 34 to read:

<%= form_with model: @quote, url: '/create-quote', id: 'rails_quote_form' do |f| %>

What we did here is remove the “local: true” parameter and give our form an id, since we’ll need to manually clear the form after it is submitted.

Now in your app>controllers>welcome_controller.rb file you can delete line 29. That is, you no longer need to “redirect_to root_path” in place.  

However, now that we’re submitting a remote form, Rails expects a “.js.erb” file in the view corresponding to our method name in order to render some custom JavaScript when the request is completed. Since we already used CableReady and CableCar to add our quote to our Rails list and React list, respectively, all we need to do now is create the file app>views>welcome>create_quote.js.erb and in it put the following single line:

document.getElementById('rails_quote_form').reset();

This will properly reset our form after we submit a quote from our Rails form.

Go ahead and review your changes - which should line up with this commit diff - and then:

  • git add .
  • git commit -a -m "refactor our Rails form to create hello world quotes remotely"
  • git push

Now, you probably noticed early on that when you are testing out your app with both the Rails list and the React list in place, clicking on a Rails quote results in the React app crashing and disappearing.

Here is a warning message that displays in the console.

What’s actually happening here is quite amazing. It’s an important learning moment. When we click to delete our quote element, what’s happening is that CableReady is removing our element so fast that our Reflex didn’t have time to properly complete its life cycle.

In addition, because StimulusReflex is attempting to re-render the page without the quote in it, this isn’t playing nicely with our Redux/React app.  

There are multiple ways around this. We could add the data-reflex-permanent parameter to our root React element to prevent it from being re-rendered incorrectly and crashing. We could also scope the re-render from our reflex to our list by adding a data-reflex-root parameter on our <li> elements or our <ul> element so it wouldn’t re-render the whole page.

However, what we’ll do instead is simply tell our reflex to not update anything on the page, since we are already using CableReady anyway to remove the element.

Go ahead and open up our app>reflexes>quotes_reflex.rb file and on a new line 4 enter and save:

morph :nothing

Now you can test your side-by-side browsers and the React app won’t disappear when you click on a quote in the StimulusReflex list.

While testing this manually I noticed we had a bug. If we click on a quote in the React list it was actually removing the last quote in the list at the same time.  Let’s go fix that bug by opening up our app>javascript>react_quotes_app>quotes_reducer.js file and replacing line 29 with this:

    if (state.indexOf(action.response.data) > -1){
      return state.remove(state.indexOf(action.response.data));
    }else{
      return state; 
    }

So the diff will look like this in VSC:

The reason why that bug was happening is because when we were broadcasting our deleted quote ID to other browsers, it was telling our own browser to delete it as well. However, since that element was already removed from our Redux store, it was removing the “indexOf” something that didn’t exist, which outputs “-1”. Since we were telling Redux to literally “remove(-1)” from our list, this was removing the last element by accident.

Let’s commit and push our fixes.  Check that your diff lines up with this commit diff and then:

  • git commit -a -m "prevent our QuotesReflex destroy method from updating the page, and fix a Redux bug that was removing our last quote"
  • git push

Good. Let’s continue to Part 8 →

Comments

Interested in participating in the comments on this article? Sign up/in to get started:

By signing up/in with Satchel you are agreeing to our terms & conditions and privacy policy.