質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

1114閲覧

socket.ioを使って受信したデータをreactで作成したページにリアルタイムで表示したい

pierogi.user

総合スコア19

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

Express

ExpressはNode.jsのWebアプリケーションフレームワークです。 マルチページを構築するための機能セットおよびハイブリッドのWebアプリケーションを提供します。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2022/03/19 10:06

編集2022/03/19 10:28

標題の通りsocket.ioを使って受信したデータをreactを使って作成したページにリアルタイムで更新・表示することを考えています。

このために考えた方法は以下の通りです。

  1. reactのcontextを使ったコンポーネント内でsocket.ioを使って初期データを受信(initial_data_to_browserイベント)。データ形式は連想配列(オブジェクトの配列)。
  2. 受信したデータはuseRefで作成したデータ更新操作用の変数(socketData)に保存し、このsocketDataをuseStateで作成した変数(contextData)に保存。このcontextDataを他のコンポーネントへ渡す変数とする。
  3. 初期データの受信後は10秒ごとに新規データを受信(test_emit_10sイベント)。新規データは連想配列ではなく1つのオブジェクト。
  4. socketDataの更新は、受信したオブジェクトを追加し、一番古いオブジェクトを削除する。
  5. 更新後のsocketDatasetContextDatacontextDataに保存。

そして、作成したコードが以下です。

dataContext.tsx(データ受信、更新用にcontextを使って作成したもの)

typescript

1import { ReactNode } from "react"; 2import {useContext, createContext, useState, useEffect, useRef} from "react"; 3import {io, Socket} from "socket.io-client"; 4 5//console.logでの日時表示用の関数 6const current_time_readable = () => { 7 let date_obj = new Date(); 8 return `${date_obj.getUTCFullYear()}-${('0' + (date_obj.getUTCMonth() + 1)).slice(-2)}-${('0' + date_obj.getUTCDate()).slice(-2)} ${('0' + date_obj.getUTCHours()).slice(-2)}:${('0' + date_obj.getUTCMinutes()).slice(-2)}:${('0' + date_obj.getUTCSeconds()).slice(-2)}`; 9}; 10 11type Props = { 12 children: ReactNode; 13} 14 15//Contextの作成。contextDataを他のコンポーネントへ渡す 16const DataContext = createContext({ 17 contextData: [] as Array<any>, 18}); 19 20//socketオブジェクトを作成して通信を開始。 21const socket: Socket = io("http://***.***.***.***"); 22 23const DataProvider = (props: Props) => { 24 //他のコンポーネントへ渡すcontextData変数をuseStateで作成 25 const [contextData, setContextData] = useState<Array<any>>([]); 26 27 //データの一時保存、更新用の変数をuseRefで作成 28 let socketData = useRef<Array<any>>([]); 29 30 useEffect(() => { 31 32 socket.on("connect_error", (e) => { 33 console.log(e); 34 }); 35 36 //初期データ受信用のsocketイベント 37 socket.on('initial_data_to_browser', (data) => { 38 //受信データをsocketDataへ保存 39 socketData.current = data; 40 41    //useRef変数の確認表示 42 console.log('initialData'); 43 console.log(socketData.current); 44 45    //contextDataへsocketDataを保存 46 setContextData(() => socketData.current); 47 48 //contextDataの確認表示(ステートの確認表示) 49 console.log('stateData'); 50 console.log(contextData); 51 }); 52 53 //新規データ受信用のsocketイベント 54 socket.on('test_emit_10s', (data) => { 55          //受信時間の表示 56 console.log(`${current_time_readable()} | test_data received`); 57 58 //古いデータを1つだけ削除 59 socketData.current.shift(); 60 61 //最新データを追加 62 socketData.current.push(data); 63 64 //データの更新結果の確認表示 65 console.log('socketData'); 66 console.log(socketData.current); 67 68 //contextDataへ更新結果を保存 69 setContextData(socketData.current); 70 71 //contextDataの確認表示(ステートの確認表示) 72 console.log('contextData'); 73 console.log(contextData); 74 }); 75 76 return () => { 77 //コンポーネントがアンマウントされた時にsocket通信を終了 78 socket.disconnect() 79 }; 80 }, []); 81 82 return ( 83 //他のコンポーネントへの出力としてcontextDataを設定 84 <DataContext.Provider value={{contextData}}> 85 {props.children} 86 </DataContext.Provider> 87 ); 88} 89 90const useData = () => useContext(DataContext); 91 92export { DataProvider, useData }

dataCard.tsx(データ表示用のコンポーネント)

typescript

1import {VFC} from "react"; 2import {Heading} from "@chakra-ui/react"; 3 4import {useData} from "../../context/dataContext"; 5import {chartDataType} from "../../types/chartDataType"; 6 7//表示データに現在時刻を付与するための関数 8const current_time_readable = () => { 9 let date_obj = new Date(); 10 return `${date_obj.getUTCFullYear()}-${('0' + (date_obj.getUTCMonth() + 1)).slice(-2)}-${('0' + date_obj.getUTCDate()).slice(-2)} ${('0' + date_obj.getUTCHours()).slice(-2)}:${('0' + date_obj.getUTCMinutes()).slice(-2)}:${('0' + date_obj.getUTCSeconds()).slice(-2)}`; 11}; 12 13type Props = { 14 dataName: string; 15}; 16 17export const ChartCard: VFC<Props> = (props) => { 18 const {dataName} = props; 19 20 //contextDataをchartDataとして受け取り 21 const chartData = useData().contextData; 22 const propNameData: Array<chartDataType> = []; 23 24 //chartDataから特定の項目のデータを抽出して新たに配列を作成 25 if (chartData) { 26 chartData.forEach((data) => { 27 propNameData.push({ 28 value: data[dataName], 29 unixTime: data['end_time_unix'], 30 }); 31 }); 32 } 33 34 return ( 35 <> 36 { 37 //データの表示 38 propNameData ? (propNameData.map((data: any) => ( 39 <Heading as="h1" key={data.unixTime}>{`${current_time_readable()} | ${data.unixTime} | ${data.value}`}</Heading> 40 ))) : null 41 } 42 </> 43 ) 44}

上記のコードだとdataContext.tsxでは socketDataの保存・更新はうまくいっているのですが、contextDataへの保存がうまくいっておらず空配列のままです(initial_data_to_browsertest_emit_10sのどちらのイベント発生時でも)。
dataCard.tsxの表示は初期データはブラウザに表示されるのですが、その後の更新データは表示されません。この点に関しては、dataContext.tsxcontextDataは空のままなのに初期データだけがdataCard.tsxに渡されているのが不思議です。
dataCard.tsxはコンポーネントの上位階層で<DataProvider>タグで囲んでいます(間にルーティングのためのコンポーネントは存在しています)。

dataCard.tsxで初期データ・更新データをリアルタイムに表示するにはどう修正すればいいでしょうか?
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Eduardo

2023/08/29 23:16

Hello, sorry for the inconvenience. I'm trying to do something similar and I can't, I'm just starting with React and socket, you can post the complete code in a repository like github, to have it as a guide. Thank you.
guest

回答1

0

ベストアンサー

useStateは値が更新されているかどうかの判定にObject.is関数を用いています。したがって、今のコードのように同一の配列をsetContextDataに渡しても、変更はないと判断されビューは更新されません。
(つまりReactは配列の中身まで調べません)

結果としてinitial_data_to_browserのときには読み込んだデータがセットされますが、それ以降のtest_emit_10sでは値が変化しなかったと判断され画面が更新されません。

次のようにスプレッド構文などを用いて配列を新しく作り直すことでビューが更新されるようになるはずです。

ts

1//新規データ受信用のsocketイベント 2socket.on('test_emit_10s', (data) => { 3 // ...省略 4 //contextDataへ更新結果を保存 5 setContextData([...socketData.current]); 6 // ...省略 7 });

dataContext.tsxでcontextDataは空のままなのに初期データだけがdataCard.tsxに渡されているのが不思議です。

これはsetContextDataを呼び出してもcontextDataが即座に更新されるわけではないからです。
また、useEffect関数の第二引数でcontextDataを指定していないので、useEffect内のcontextDataは初回実行時のものが利用され続けます。したがって実際にはcontextDataが更新されていたとしてもconsole画面には空の配列が表示されます。

余談ですが、連想配列とオブジェクトの配列は別物です。今回のはオブジェクトの配列ですよね。
あと、socketDataはなんのために用意されているのでしょうか。私なら下記のようにcontextDataにそのままデータを突っ込んでしまいます。

ts

1//初期データ受信用のsocketイベント 2socket.on("initial_data_to_browser", (data) => { 3 setContextData(data); 4}); 5 6//新規データ受信用のsocketイベント 7socket.on("test_emit_10s", (data) => { 8 setContextData(prev => [...prev.slice(1), data]); 9});

投稿2022/03/22 16:33

編集2022/03/22 17:06
miyaharu

総合スコア77

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pierogi.user

2022/03/23 05:38

回答ありがとうございます。おかげで意図通りの動作をするようになりました。 contextDataにデータを直接書き込む方法も教えていただきありがとうございます。 これでしたらsocketDataは不要ですね。 ありがとうございました。
miyaharu

2022/03/23 11:17

報告ありがとうございます。お力になれたようで良かったです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問