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

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

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

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

React.js

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

Q&A

解決済

1回答

820閲覧

react useStateの非同期|intervalで書き換えを行うと再描画されない問題

yutadd

総合スコア18

TypeScript

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

React.js

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

0グッド

0クリップ

投稿2022/11/14 08:46

編集2022/11/15 09:00

前提

setIntervalで5秒毎に非同期でAPIからJSONでデータを取得し、それを要素に変換して表示しているのですが、再描画がされず、困ってます。
デバッグ用にforceUpdateというboolean型のstateを使い、強制再描画&更新の確認をしてみたところ、初期値falseが一度trueに書き換わり、描画され、それ以降二度とtrueに戻りません。

typescript

1import React, { useState, useEffect, useContext, useReducer } from "react"; 2import CommentCard from "./CommentCard"; 3import "./Home.css"; 4import { Context } from "./App"; 5 6import Box from "@mui/material/Box"; 7import { CommentSharp } from "@material-ui/icons"; 8 9const update = (state: any, additionalElement: any) => { 10 console.log("add↓"); 11 console.log(additionalElement); 12 console.log("state"); 13 console.log(state); 14 let _tmp = state; 15 _tmp.left.push(additionalElement); 16 return _tmp; 17}; 18function Home() { 19 const [right, setRight] = useContext(Context); 20 const [forceUpdate, setforceUpdata] = useState<boolean>(false); 21 22 const [state, setState] = useReducer(update, { left: [] }); 23 24 useEffect(() => { 25 setInterval(() => { 26 const getter = async () => { 27 const promise = await fetch("/api/share/get/comments"); 28 const json = await promise.json(); 29 console.log(state.left); 30 for (var a = 0; a < json.length; a++) { 31 setState( 32 <CommentCard 33 key={json[a]["commentID"]} 34 user={json[a]["username"]} 35 json={json[a]} 36 /> 37 ); 38 } 39 }; 40 getter(); 41 setforceUpdata(forceUpdate ? false : true); 42 }, 5000); 43 }, []); 44 45 return ( 46 <> 47 <Box sx={{ pt: "1vh" }} className="left"> 48 {state.left} 49 {forceUpdate ? "true" : "false"} 50 </Box> 51 52 <Box className="left">{right}</Box> 53 </> 54 ); 55} 56export default Home; 57

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

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

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

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

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

guest

回答1

0

ベストアンサー

userNames_cardsのような、関数コンポーネント内で普通に宣言した変数は、

  • コンポーネントの描画ごとに初期化が行われる
  • 非同期な読み書きを行う場合クロージャとなるので、非同期処理開始時の変数をキャプチャしてしまい、完了時に書き込んでも実質意味をなさない

などの事情がありますので、非同期実行の結果を入れるために使うことはできません。stateあるいはrefとしてください。

投稿2022/11/14 09:09

maisumakun

総合スコア145183

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

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

yutadd

2022/11/14 11:26 編集

すみません、指摘通り、 comments usernamesを stateに変更し、 コンソールヘ値を出力する変更を下記のように実装してみましたが、 描画されず、 コンソールへの出力: (2) [{…}, {…}] comments [] left undefined となっており、 commentsおよびleftが、正しく更新や初期化が行われていないか、アクセスできないです コード: function Home() { const [right, setRight] = useContext(Context); const [comments, setComments] = useState<any[]>([]); const [userNames, setUserNames] = useState<string[]>([""]); const [left, setLeft] = useState<JSX.Element[]>(); useEffect(() => { console.log("start effect"); setInterval(() => { fetch("/api/share/get/comments").then((promise) => { promise.json().then((json) => { console.log(json); setComments(json); let unames: string[] = []; for (let a = 0; a < json.length; a++) { let uid = json[a]["userID"]; fetch("/api/share/get/getuser?uid=" + uid, { mode: "cors" }).then( (prename) => { prename.json().then((user) => { unames.push(user["name"]); setUserNames(unames); }); } ); } }); }); }, 5000); setTimeout(() => { setInterval(() => { let _cards: JSX.Element[] = []; console.log("comments"); console.log(comments); console.log("left"); console.log(left); for (let i = 0; i < comments.length; i++) { _cards.push( <CommentCard key={comments[i]["commentID"]} user={userNames[i]} json={comments[i]} /> ); console.log(Object.is(_cards, left)); } setLeft(_cards); }, 5000); }, 500); }, []);
maisumakun

2022/11/14 11:32

上に書いたように、非同期実行ではクロージャが変数を束縛してしまうので、setTimeoutの中のコールバックではstateであっても、参照が正しく行えません。 setLeftのほうは、なぜsetTimeoutやsetIntervalで囲んでいるのでしょうか。
yutadd

2022/11/14 11:58 編集

なるほど、非同期だとStateでも更新がうまくいかないんですね 非同期で5秒毎に[{"userID":"hogehoge","commentID":"7ff5b60cc4933c95e09508f1f3f3761fff0891c2972458982cef7c585e469cf5","file1":"none","text":"konnnitiwa","time":"11月 14, 2022","likes":0},{"userID":"hogehoge",..}]このようなJSONのデータをを取得して描画する方法がわかりません。。 ちなみに、1つ目のループでデータの取得を行い、 2つ目のループでデータの変換と描画する変数に登録を行っていました。その際、同時に変数にアクセスしないように、timeoutを500ミリ秒行ってずらしていました。
maisumakun

2022/11/14 12:27

> その際、同時に変数にアクセスしないように、timeoutを500ミリ秒行ってずらしていました。 そんな配慮は必要ありません。Reactの標準的なやり方としては、fetchで取得・stateへのセットだけuseEffectで行って、stateにセットされたデータは「そのまま」描画する、という流れになります。stateが更新されれば再描画は自動で行われます。
yutadd

2022/11/15 01:07 編集

なるごど。 以下ようなシンプルな実装(コード1)に変更してみても、表示されないのはやはりおっしゃる通り、fetchが非同期実行なため初期の空データが入るからですかね? そこで、少し改良して、2個めのfetch以下をコード2のようにしてみたのですが(コード2)、すると1つのみが表示されしまいました...left参照できないからですよね どうすればAPIから取ってきたデータからもう一度APIへアクセスし、それらのデータをオブジェクトに変換し、描画できるんでしょうか... コード1: useEffect(() => { let elements: JSX.Element[] = []; setInterval(() => { fetch("/api/share/get/comments").then((promise) => { promise.json().then((json) => { console.log(json); for (let a = 0; a < json.length; a++) { let uid = json[a]["userID"]; fetch("/api/share/get/getuser?uid=" + uid, { mode: "cors" }).then( (prename) => { prename.json().then((user) => { elements.push( <CommentCard key={json[a]["commentID"]} user={user["name"]} json={json[a]} /> ); }); } ); } setLeft(elements); }); }); }, 5000); }, []); コード2 (prename) => { prename.json().then((user) => { setLeft([ ...(left as JSX.Element[]), <CommentCard key={json[a]["commentID"]} user={user["name"]} json={json[a]} />, ]); }); }
yutadd

2022/11/14 14:33

useReducerとかで追加処理を用意するのが最善ですかね
yutadd

2022/11/15 08:25

変更>>質問をより正確に書き換えました
maisumakun

2022/11/15 08:41

setforceUpdata(forceUpdate ? false : true);と非同期コールバック内で書いてしまうと、forceUpdateもキャプチャされた値なので、現在の値と全く関係ない値が残ってしまいます。 setforceUpdata(old => !old);と、現在の値を見て変更する関数を渡してください。
maisumakun

2022/11/15 09:07

あと、updateで破壊的な変更を行っていますが、これを行うとReactで更新を認識できません(let _tmp = state;はオブジェクトのコピーを行いません)。
yutadd

2022/11/15 09:21

すみません、そうでしたそうでした。 お、trueとfalseの値は交互に再描画されるようになりました。 このsetforceUpdata((old) => !old);をいろいろな場所に配置してみたんですが、 asyncをつけたgetter関数のスコープにおいた場合、booleanの再描画がされました。が、 その中のfor文の中だと再描画が起こらないようです。
yutadd

2022/11/15 11:02 編集

お陰様で正しく描画が行われるようになりました! ご指摘をいただいた破壊的変更をきちんと加えていなかったこと、値が更新される前のstateの値を参照していたこと、関数コンポーネントの変数を参照していたことなどが等が原因でした。 function Home() { const [right, setRight] = useContext(Context); const [tmp_l, setTmp_l] = useState<JSX.Element[]>([<></>]); useEffect(() => { setInterval(() => { const getter = async () => { const promise = await fetch("/api/share/get/comments"); const json = await promise.json(); console.log(json.length); for (let a = 0; a < json.length; a++) { setTmp_l((previous: JSX.Element[] | undefined) => { let tmp: JSX.Element[] = [...(previous as JSX.Element[])]; console.log(json[a]); tmp.push( <CommentCard key={json[a]["c"]["commentID"]} user={json[a]["username"]} json={json[a]} /> ); return tmp; }); } }; getter(); }, 5000); }, []); return ( <> <Box sx={{ pt: "1vh" }} className="left"> {tmp_l} </Box> <Box className="left">{right}</Box> </> ); } export default Home;
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問