実現したいこと
ReactとLaravelやっている人がいたら相談したいことがあります・・・。
現在Youtubeの配信のコメント欄のようにリアルタイムでコメントが流れるような機能を作ろうとしています。
それでPusherというSaasを用いてWebSocket通信をしてリアルタイムコメントを実装しようと思っています。
ただ、片方のタブで送信したコメントを、もう片方のタブで開いた時に一度再読み込みをしないと反映されません。
映像のように、Command+Rで再読み込みをしなければ反映されません。
発生している問題・エラーメッセージ
エラーメッセージ
該当のソースコード
ルーティング
api.php
1<?php 2 3use Illuminate\Http\Request; 4use Illuminate\Support\Facades\Route; 5use App\Http\Controllers\CommentController; 6/* 7|-------------------------------------------------------------------------- 8| API Routes 9|-------------------------------------------------------------------------- 10| 11| Here is where you can register API routes for your application. These 12| routes are loaded by the RouteServiceProvider and all of them will 13| be assigned to the "api" middleware group. Make something great! 14| 15*/ 16 17Route::middleware('auth:sanctum')->get('/user', function (Request $request) { 18 return $request->user(); 19}); 20 21// 既存のコメント取得エンドポイント 22Route::get('/comments', [CommentController::class, 'index'])->name('get.broadcastingRooms.comment'); 23 24// 新しいコメント作成エンドポイント 25Route::post('/comments', [CommentController::class, 'store'])->name('store.broadcastingRooms.comment'); 26
CommentController.php
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Events\SentComment; 6use App\Models\Comment; 7use Illuminate\Http\Request; 8use Illuminate\Support\Facades\Event; 9use Illuminate\Support\Facades\Log; 10class CommentController extends Controller 11{ 12 public function index(Request $request) 13 { 14 $comments = Comment::all(); 15 event(new SentComment($comments)); 16 return response()->json($comments); 17 } 18 19 public function store(Request $request) 20 { 21 $this->validate($request, [ 22 'text' => 'required', 23 ]); 24 $commentModel = new Comment(); 25 $comment = $commentModel->insertComment($request); 26 event(new SentComment($comment)); 27 return response()->json(); 28 } 29} 30
Comment.php
1<?php 2namespace App\Models; 3 4use Illuminate\Database\Eloquent\Factories\HasFactory; 5use Illuminate\Database\Eloquent\Model; 6use Illuminate\Database\Eloquent\Relations\BelongsTo; 7use Illuminate\Support\Facades\Log; 8 9class Comment extends Model 10{ 11 use HasFactory; 12 13 protected $fillable = ['comment']; // フィールド名を修正 14 protected $table = 'broadcasting_rooms_comments'; 15 16 public function broadcastingRoom(): BelongsTo 17 { 18 return $this->belongsTo(BroadcastingRoom::class); 19 } 20 21 public function insertComment($request) 22 { 23 $requestBody = file_get_contents('php://input'); 24 25 // JSON文字列を連想配列に変換 26 $jsonData = json_decode($requestBody, true); 27 28 // "text" フィールドの値を取得 29 $commentText = $jsonData['text']; 30 $comment = new Comment(); 31 $referer = $request->headers->get('referer'); // リクエストの Referer を取得 32 //refererが配信者・視聴者のどちらかのURLかを判定 33 if (strpos($referer, "http://localhost/broadcast/") !== false) { 34 //視聴者の場合 35 if(strpos($referer, "http://localhost/broadcast/stream") !== false) { 36 $pattern = "http://localhost/broadcast/stream/"; 37 $broadcastId = str_replace($pattern, "", $referer); 38 $comment->broadcasting_rooms_id = $request->input('broadcasting_rooms_id', (int)$broadcastId); 39 $comment->comment = $request->input('comment', $commentText); 40 $comment->save(); 41 return $comment; 42 //配信者の場合 43 } else { 44 $pattern = "http://localhost/broadcast/"; 45 $broadcastId = str_replace($pattern, "", $referer); 46 $comment->broadcasting_rooms_id = $request->input('broadcasting_rooms_id', (int)$broadcastId); 47 $comment->comment = $request->input('comment', $commentText); 48 $comment->save(); 49 return $comment; 50 } 51 } 52 } 53} 54
下記が配信者用の画面です
BroadcastRoom.jsx
1import React, { useEffect, useState } from 'react'; 2import FileTree from './FolderTree/FileTree'; 3import Editor from './Editor'; 4import CommentForm from './Comment/CommentForm'; 5import CommentList from './Comment/CommentList'; 6import AudioStreamer from './Audio/AudioStreamer'; 7 8const BroadcastRoom = () => { 9 const [fileNames, setFileNames] = useState([]); 10 const [comments, setComments] = useState([]); 11 12 useEffect(() => { 13 // URLのパラメーターを使用してAPIからデータを取得するなどの処理をここに記述 14 fetch('/api/comments') 15 .then((response) => response.json()) 16 .then((data) => { 17 setComments(data); 18 }); 19 20 // Pusherを使ったリアルタイムコメントのリッスンを追加 21 window.Echo.channel('comment') 22 .listen('.SentComment', (e) => { 23 setComments(prevComments => [...prevComments, e.comment]); 24 }); 25 }, []); 26 27 const addComment = (newComment) => { 28 // 新しいコメントオブジェクトにIDを追加する 29 const commentWithId = { ...newComment }; 30 31 fetch('/api/comments', { 32 method: 'POST', 33 headers: { 34 'Content-Type': 'application/json', 35 }, 36 // IDを含めたコメントオブジェクトをJSON文字列に変換してリクエストのボディに設定する 37 body: JSON.stringify(commentWithId), 38 }) 39 .then((response) => response.json()) 40 .then((data) => { 41 setComments([...comments, data]); 42 fetch('/api/comments') 43 .then((response) => response.json()) 44 .then((data) => setComments(data)); 45 }); 46 }; 47 48 return ( 49 <div className='all-space'> 50 <AudioStreamer /> 51 <div style={{ display: 'flex' }}> 52 <FileTree fileNames={ fileNames } setFileNames={ setFileNames } /> 53 <div style={{ flex: 1 }}> 54 <Editor selectedFiles={ fileNames } /> 55 </div> 56 <div className='comments'> 57 <CommentList comments={ comments } updateComments={ setComments } /> 58 <CommentForm onAddComment={ addComment } /> 59 </div> 60 </div> 61 </div> 62 ); 63}; 64 65export default BroadcastRoom; 66
ViewerDashboard.jsx
1import React, { useEffect, useState} from 'react'; 2import CommentForm from './Comment/CommentForm'; 3import CommentList from './Comment/CommentList'; 4 5const ViewerDashboard = () => { 6 const [comments, setComments] = useState([]); 7 8 useEffect(() => { 9 fetch('/api/comments') 10 .then((response) => response.json()) 11 .then((data) => setComments(data)); 12 13 // Pusherを使ったリアルタイムコメントのリッスンを追加 14 window.Echo.channel('comment') 15 .listen('.SentComment', (e) => { 16 setComments(prevComments => [...prevComments, e.comment]); 17 }); 18 }, []); 19 20 const addComment = (newComment) => { 21 fetch('/api/comments', { 22 method: 'POST', 23 headers: { 24 'Content-Type': 'application/json', 25 }, 26 body: JSON.stringify(newComment), 27 }) 28 .then((response) => response.json()) 29 .then((data) => { 30 setComments([...comments, data]); 31 fetch('/api/comments') // GETリクエストを実行してコメント一覧を更新 32 .then((response) => response.json()) 33 .then((data) => setComments(data)); 34 }); 35 }; 36 37 return ( 38 <div> 39 <h1>視聴者用の画面</h1> 40 <CommentList comments={ comments } updateComments={ setComments } /> 41 <CommentForm onAddComment={ addComment } /> 42 </div> 43 ); 44}; 45 46export default ViewerDashboard; 47
下記がイベントです。
SentComment.php
1<?php 2namespace App\Events; 3use Illuminate\Support\Facades\Log; 4use Illuminate\Broadcasting\Channel; 5use Illuminate\Broadcasting\InteractsWithSockets; 6use Illuminate\Contracts\Broadcasting\ShouldBroadcast; 7use Illuminate\Foundation\Events\Dispatchable; 8use Illuminate\Queue\SerializesModels; 9 10class SentComment implements ShouldBroadcast 11{ 12 use Dispatchable, InteractsWithSockets, SerializesModels; 13 14 /** 15 * The new comment. 16 * 17 * @var \App\Models\Comment 18 */ 19 public $comment; 20 21 /** 22 * Create a new event instance. 23 * 24 * @param \App\Models\Comment $comment 25 */ 26 27 public function __construct($comment) 28 { 29 $this->comment = $comment; 30 } 31 32 /** 33 * Get the channels the event should broadcast on. 34 * 35 * @return array<int, \Illuminate\Broadcasting\Channel> 36 */ 37 38 public function broadcastOn(): array 39 { 40 return [new Channel('comment')]; 41 42 } 43} 44
bootstrap.js
1/** 2 * We'll load the axios HTTP library which allows us to easily issue requests 3 * to our Laravel back-end. This library automatically handles sending the 4 * CSRF token as a header based on the value of the "XSRF" token cookie. 5 */ 6 7/** 8 * Echo exposes an expressive API for subscribing to channels and listening 9 * for events that are broadcast by Laravel. Echo and event broadcasting 10 * allows your team to easily build robust real-time web applications. 11 */ 12 13 import Echo from 'laravel-echo'; 14 15 import Pusher from 'pusher-js'; 16 window.Pusher = Pusher; 17 18 window.Echo = new Echo({ 19 broadcaster: 'pusher', 20 key: import.meta.env.VITE_PUSHER_APP_KEY, 21 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 22 wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 23 wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 24 wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 25 forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 26 enabledTransports: ['ws', 'wss'], 27 }); 28
試したこと
Pusherのデバッグ用のログを確認しました。
2つのViewを別のタブで開いている状態で、Subscriptionのログがあるのかを見てみたのですが、片方の画面でコメントが反映されたとしてももう片方の画面ではリロードしなければSubscriptionの処理がされません。
公式ドキュメントのやり方でbootstrap.jsのコメントアウトを外すというのは見てやってみたのですができませんでした。
環境
Laravel10
laravel-sail
React.js(Laravelに付属しているReactです。別のReactアプリケーションとして立ち上げているわけではありません。同じコンテナ内で動いています。)
よろしくお願いいたします。

バッドをするには、ログインかつ
こちらの条件を満たす必要があります。