I have setup WebSockets through ActionCable for my Rails API/React Front End application and I am struggling with some real odd functionality. Currently, my <ActionCableConsumer />
is firing randomly. Let's say I have four browsers open and I submit a form that should render new data to all four browsers, two may stream the data from the channel, while the other two render nothing. Or one of the browsers may render the data more than once, while the other three render nothing. It truly is a sporadic deal.
Here is where I take the data from the subscription and update the state:
handleReceived = (message) => {
console.log('check me')
this.setState({
apiData: [...this.state.apiData, message.pickup_delivery]
});
}
render(){
return(
<div>
<ActionCableConsumer channel={{channel: 'PickupDeliveriesChannel'}} onReceived={this.handleReceived}>
<div>
<CustomersForm showCustForm={this.state.showCustForm} handleClose={this.hideForm} addNewCustomer={this.addNewCustomer} />
<Calendar getCustForm={this.getCustForm} getForm={this.getForm} deleteLoad={this.deleteLoad} {...this.state} seededColorGenerator={this.seededColorGenerator} />
<PickupDeliveriesForm getFormColor={this.getFormColor} deleteCustomer={this.deleteCustomer} handleClose={this.hideForm} showForm={this.state.showForm} updateLoad={this.updateLoad} {...this.state} onNewLoad={this.addNewLoad} seededColorGenerator={this.seededColorGenerator} />
</div>
</ActionCableConsumer>
</div>
)
}
}
Here is what my controller looks like:
module Api::V1
class PickupDeliveriesController < ApplicationController
before_action :set_pickup_delivery, only: [:show, :update, :destroy]
# GET /pickup_deliveries
def index
@pickup_deliveries = PickupDelivery.all
render json: @pickup_deliveries
end
# GET /pickup_deliveries/1
def show
render json: @pickup_delivery
end
# POST /pickup_deliveriesPickupDeliveries
def create
pickup_deliveries = PickupDelivery.new(pickup_delivery_params)
if pickup_deliveries.save
serialized_data = ActiveModelSerializers::Adapter::Json.new(
PickupDeliveriesSerializer.new(pickup_deliveries)
).serializable_hash
ActionCable.server.broadcast 'pickup_deliveries_channel', serialized_data
head :ok
end
end
# PATCH/PUT /pickup_deliveries/1
def update
if @pickup_delivery.update(pickup_delivery_params)
render json: @pickup_delivery
else
render json: @pickup_delivery.errors, status: :unprocessable_entity
end
end
# DELETE /pickup_deliveries/1
def destroy
@pickup_delivery.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_pickup_delivery
@pickup_delivery = PickupDelivery.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def pickup_delivery_params
params.require(:pickup_delivery).permit(:id, :pickup_date, :pickup_location, :rate, :delivery_date, :delivery_location, :local_delivery, :local_pickup, :loaded_miles, :deadhead_miles, :delivery_id, :pickup_zip, :delivery_zip, :hazmat, :sameday, :delivery_time, :pickup_time, :aurora_number, :out_of_route, :round_trip, :customer_id, :color)
end
end
end
Here is my actual channel:
class PickupDeliveriesChannel < ApplicationCable::Channel
def subscribed
stream_from 'pickup_deliveries_channel'
end
end
I am not specifying any users because I don't need to yet. Basically, when a client submits a new load on the load board, I need all the other load boards to display the new instance. Currently, it is not working like that all.
With five browsers open and after one form submission on a random browser, three out of the five browsers rendered the correct data (one load), one browser rendered nothing and one browser rendered the data TWICE.
It is pure chaos lol.
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
PickupDeliveriesChannel transmitting {"pickup_delivery"=>{"id"=>17, "pickup_date"=>"2019-11-16", "pickup_location"=>"", "rate"=>nil, "delivery_date"=>"2019-11-16", "delivery_location"=>"", "local_delivery"=>false, "local_pickup"=>false, "loaded_miles"=>nil, "deadhead_miles"=>nil, "delivery_id"=>nil, "pickup_zip"=>"", "delivery_zip"=... (via streamed from pickup_deliveries_channel)
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
Could not execute command from ({"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"PickupDeliveriesChannel\"}"}) [RuntimeError - Unable to find subscription with identifier: {"channel":"PickupDeliveriesChannel"}]: C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:78:in `find' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:46:in `remove' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb:18:in `execute_command' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/base.rb:87:in `dispatch_websocket_message' | C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb:60:in `block in invoke'
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
Unsubscribing from channel: {"channel":"PickupDeliveriesChannel"}
PickupDeliveriesChannel stopped streaming from pickup_deliveries_channel
PickupDeliveriesChannel is transmitting the subscription confirmation
PickupDeliveriesChannel is streaming from pickup_deliveries_channel
On that specific submission, here is what my rails console looks like.
It is pure chaos and I have no idea how to make it flow correctly.
EDIT: The more form submissions I go through, the more subscriptions are being opened. It's almost like every time I open the form to submit something, another subscription is streaming? With two browsers open, I can be transmitting any number of objects...
Here are my routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :pickup_deliveries
resources :customers
end
end
mount ActionCable.server => '/cable'
end
Here is my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { ActionCableProvider } from 'react-actioncable-provider';
import * as serviceWorker from './serviceWorker';
import { API_WS_ROOT } from './constants';
ReactDOM.render(
<ActionCableProvider url={API_WS_ROOT}>
<App />
</ActionCableProvider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
Here are the constants that I am using
export const API_ROOT = 'http://localhost:3001/api/v1';
export const API_WS_ROOT = 'ws://localhost:3001/cable';
export const HEADERS = {
'Content-Type': 'application/json',
Accept: 'application/json',
};
So, I figured it out. I sent the current state that contained all my fetched instances VIA params
from the front end to the channel and compared it with the current database. To be honest, I am not exactly sure as to why it worked. Basically, I said if params[:all_loads]
(current fetched response in the front end) is equal to the amount of instances in the back end object, then continue to stream to channel. The solution is below:
class PickupDeliveriesChannel < ApplicationCable::Channel
def subscribed
if params[:all_loads].length == PickupDelivery.all.length
stream_from 'pickup_deliveries_channel'
end
end
def unsubscribed
end
end