I
I
Ismail2020-04-21 16:16:03
Ruby on Rails
Ismail, 2020-04-21 16:16:03

How can I solve the problem of binding React + Rails through ActionCable?

Good time of the day! There is a problem with React and Rails communication via ActionCable . The essence of the problem:

React Front-End is completely separated from the Rails API , i.e. react_on_rails , etc. is not used. You need to connect the front and back via a web socket, i.e. ActionCable . Front end connects:

network cable log

5e9eef3bb8a52772114618.png


Next, I open a second tab, create a List . And in the end, nothing appeared on the first tab.
This is the point, the react seems to be connected, but it does not receive information from the server.

Below are important logs, etc.

development.log GET

Finished "/api/v1/cable/" [WebSocket] for 172.18.0.1 at 2020-04-21 13:06:41 +0000
BoardsChannel stopped streaming from boards:boards_channel
Started GET "/api/v1/boards" for 172.18.0.1 at 2020-04-21 13:06:42 +0000
Processing by Api::V1::BoardsController#index as HTML
  [1m[36mBoard Load (0.9ms)[0m  [1m[34mSELECT "boards".* FROM "boards"[0m
  ↳ app/controllers/api/v1/boards_controller.rb:4:in `index'
[active_model_serializers]   [1m[36mList Load (0.9ms)[0m  [1m[34mSELECT "lists".* FROM "lists" WHERE "lists"."board_id" = $1[0m  
[active_model_serializers]   ↳ app/controllers/api/v1/boards_controller.rb:4:in `index'
...

Started GET "/api/v1/lists" for 172.18.0.1 at 2020-04-21 13:06:43 +0000
Processing by Api::V1::ListsController#index as HTML
[active_model_serializers]   [1m[36mList Load (1.1ms)[0m  [1m[34mSELECT "lists".* FROM "lists" ORDER BY "lists"."created_at" ASC[0m
[active_model_serializers]   ↳ app/controllers/api/v1/lists_controller.rb:6:in `index'
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (28.21ms)
Completed 200 OK in 30ms (Views: 28.2ms | ActiveRecord: 1.1ms | Allocations: 7927)


Started GET "/api/v1/cable" for 172.18.0.1 at 2020-04-21 13:06:43 +0000
Started GET "/api/v1/cable/" [WebSocket] for 172.18.0.1 at 2020-04-21 13:06:43 +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Started GET "/api/v1/cards" for 172.18.0.1 at 2020-04-21 13:06:43 +0000
Processing by Api::V1::CardsController#index as HTML
[active_model_serializers]   [1m[36mCard Load (1.2ms)[0m  [1m[34mSELECT "cards".* FROM "cards"[0m
[active_model_serializers]   ↳ app/controllers/api/v1/cards_controller.rb:6:in `index'
[active_model_serializers] Rendered ActiveModel::Serializer::Null with Hash (45.79ms)
Completed 200 OK in 48ms (Views: 45.8ms | ActiveRecord: 1.2ms | Allocations: 12124)


Unsubscribing from channel: {"channel":"BoardsChannel"}
Could not execute command from ({"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"BoardsChannel\"}"}) [RuntimeError - Unable to find subscription with identifier: {"channel":"BoardsChannel"}]: /bundle_cache/gems/actioncable-6.0.2.2/lib/action_cable/connection/subscriptions.rb:74:in `find' | /bundle_cache/gems/actioncable-6.0.2.2/lib/action_cable/connection/subscriptions.rb:46:in `remove' | /bundle_cache/gems/actioncable-6.0.2.2/lib/action_cable/connection/subscriptions.rb:18:in `execute_command' | /bundle_cache/gems/actioncable-6.0.2.2/lib/action_cable/connection/base.rb:87:in `dispatch_websocket_message' | /bundle_cache/gems/actioncable-6.0.2.2/lib/action_cable/server/worker.rb:59:in `block in invoke'
BoardsChannel is transmitting the subscription confirmation
BoardsChannel is streaming from boards:boards_channel


Here you can see an error that does not google in any way:
Could not execute command from ({"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"BoardsChannel\"}"}) [RuntimeError - Unable to find subscription with identifier: {"channel":"BoardsChannel"}]


BoardsChannel - I have one channel for three models. If this is wrong, please correct me :)

BoardsChannel

class BoardsChannel < ApplicationCable::Channel
  def subscribed
    stream_for "boards_channel"
  end

  def unsubscribed
    stop_all_streams
  end
  
  def received(data)
    ActionCable.server.broadcast("boards_channel", data)
  end
end



Controllers:

1.
BoardsController

module Api::V1
  class BoardsController < ApplicationController
    def index 
      render json: Board.all
    end
    
    def show
      @board = Board.find(params[:id])
      render json: { board: @board }, status: :ok
    end

    def create
      @board = Board.new(link: SecureRandom.urlsafe_base64[0..9])
      
    if @board.save
      serialized_data = ActiveModelSerializers::Adapter::Json.new(
        BoardSerializer.new(@board)
      ).serializable_hash
      ActionCable.server.broadcast 'boards_channel', serialized_data
      render json: @board, root: "board", status: :ok
    end

    end
  end
end


2.
ListsController

module Api::V1
  class ListsController < ApplicationController

    def index
      @lists = List.sorted
      render json: { lists: @lists }, status: :ok
    end

    def show
      @list = List.find(params[:id])
      render json: { list: @list }, status: :ok
    end

    def create
      @list = List.new(list_params)

      @board = Board.find(list_params[:board_id])

      if @list.save
        serialized_data = ActiveModelSerializers::Adapter::Json.new(
          ListSerializer.new(@list)
        ).serializable_hash
        ActionCable.server.broadcast 'boards_channel', serialized_data
        render json: @list, root: "list", status: :ok
      end
    end

    def update
      @list = List.find(params[:id])

      if @list.update(list_params)
        render json: @list, status: :ok
      else
        render json: { errors: @list.errors }, status: :unprocessable_entity
      end
    end

    def destroy
      @list = List.find(params[:id])

      if @list.present?
        @list.destroy
        head :no_content
      end
    end

    private

    def list_params
      params.require(:list).permit(:board_id, :title)
    end
  end
end



A container that processes logic (in this case, for a board that contains a state of sheets), etc.:

BoardsContainer

import React from "react";
import axios from "axios";
import update from "immutability-helper";
import { ActionCableConsumer } from "react-actioncable-provider";

import { API_ROOT } from "../../../constants";
import Board from "./components/Board";

export default class BoardContainer extends React.Component {
  constructor() {
    super();

    this.state = {
      lists: [],
    };

    this.createList = this.createList.bind(this);
    this.handleReceivedLists = this.handleReceivedLists.bind(this);

    this.goBack = this.goBack.bind(this);
    this.deleteList = this.deleteList.bind(this);
  }

  goBack() {
    window.location.href = "/";
  }

  handleReceivedLists = (response) => {
    console.log(response);
    const { list } = response;
    this.setState({
      lists: [...this.state.lists, list],
    });
  };

  componentDidMount = () => {
    axios.get(`${API_ROOT}/lists`).then((response) => {
      const res = response.data.lists;
      this.setState({ lists: res });
    });
  };

  createList() {
    axios
      .post(`${API_ROOT}/lists`, {
        list: {
          board_id: this.props.board_id,
          title: "Sample list",
        },
      })
      .then((response) => {
        const lists = this.state.lists.concat(response.data);
        this.setState({ lists: lists });
      });
  }

  deleteList = (id) => {
    axios
      .delete(`${API_ROOT}/lists/${id}`)
      .then(() => {
        const listIndex = this.state.lists.findIndex((x) => x.id === id);
        const lists = update(this.state.lists, { $splice:  });
        this.setState({ lists: lists });
      })
      .catch((error) => console.error(error));
  };

  render() {
    return (
      <div>
        <ActionCableConsumer
          channel={{ channel: "BoardsChannel" }}
          onConnected={() => "Connected to server."}
          onReceived={this.handleReceivedLists}
        >
          <Board
            goBack={this.goBack}
            createList={this.createList}
            allLists={this.state.lists}
            board_id={this.props.board_id}
            deleteList={this.deleteList}
            handleReceivedLists={this.handleReceivedLists}
          />
        </ActionCableConsumer>
      </div>
    );
  }
}



Brought as much important and useful information as possible. I assume that the problem is on the back . But I don't understand why.

I thank you very much in advance for your help. <3

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question