Answer the question
In order to leave comments, you need to log in
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:
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
Could not execute command from ({"command"=>"unsubscribe", "identifier"=>"{\"channel\":\"BoardsChannel\"}"}) [RuntimeError - Unable to find subscription with identifier: {"channel":"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
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
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
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>
);
}
}
Answer the question
In order to leave comments, you need to log in
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question