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

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

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

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

Q&A

1回答

8192閲覧

React Hooksで値の更新が反映されない

退会済みユーザー

退会済みユーザー

総合スコア0

React.js

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

0グッド

0クリップ

投稿2020/03/09 12:21

前提・実現したいこと

IEX CloudのAPIを使って、シンプルな株のポートフォリオのウェブアプリケーションを作ろうとしています。
こちらのQiitaの記事を参考にさせて頂き、MATERIAL-UIを使って、テーブルのソート機能を実装しております。

発生している問題・エラーメッセージ

SortableTable.jsのSortableTable関数の中で、引数のdataをそのままconsole.logで出力しているのですが、一度目の表示で、値が更新されていないのが分かりました。どの様にすれば、React Hooksで、一度目から値を更新できるのでしょうか?
また、そもそもなぜログが無限に出てきてしまうのかも分かりません。
値の更新は、App.jsのaddStock関数で行っております。

コンソール

また、React Hooksを使って、APIから取ってきた値をどの様に保存しておけばいいのかも分かっておりません。useStateでいくつか配列を用意しておいた場合、どの様に値を追加していけばいいのでしょうか?

該当のソースコード

App.js

React

1import React, { useState } from 'react'; 2import { makeStyles } from "@material-ui/core/styles"; 3import SortableTable from "./SortableTable.js"; 4 5 6const useStyles = makeStyles({ 7 table: { 8 width: "60%" 9 } 10}); 11 12 13export default function App() { 14 const [listOfStocks, setListOfStocks] = useState([]) 15 16 const [state, setState] = useState({ 17 symbol: [], 18 name: [], 19 price: [], 20 num_shares: [], 21 totalValue: [] 22 }); 23 24 function addStock() { 25 const url = urlForIEXCloud("aapl") 26 27 fetch(url).then((r) => r.json()).then((data) => { 28 setState({ 29 symbol: data.symbol, 30 name: data.companyName, 31 price: data.iexRealtimePrice, 32 share: 10, 33 totalValue: 200 34 }); 35 }); 36 } 37 38 function generateTableData(numRow) { 39 const data = [...Array(numRow).keys()].map(i => ({ 40 "Symbol": state.symbol, 41 "Company Name": state.name, 42 "Real Time Price": state.price, 43 "Number of Shares": state.share, 44 "Total Value": state.totalValue 45 })); 46 return data; 47 } 48 49 const classes = useStyles(); 50 addStock(); 51 const data = generateTableData(1); 52 53 return <SortableTable data={data} className={classes.table} />; 54} 55 56 57function urlForIEXCloud(symbol) { 58 const base_url = "https://cloud.iexapis.com/" 59 const version = "stable/" 60 const endpoint_path = `stock/${symbol}/quote` 61 const token = "pk_b56bbfd8b3d54c2c9417a27e8e35b64f" 62 const query_string_params = `?token=${token}` 63 return `${base_url}${version}${endpoint_path}${query_string_params}` 64}

SortableTable.js

import React, { useState } from "react"; import PropTypes from "prop-types"; import { makeStyles } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; import TableHead from "@material-ui/core/TableHead"; import TableCell from "@material-ui/core/TableCell"; import TableRow from "@material-ui/core/TableRow"; import TableBody from "@material-ui/core/TableBody"; import TableSortLabel from "@material-ui/core/TableSortLabel"; import Paper from "@material-ui/core/Paper"; const useStyles = makeStyles({ root: { overflowX: "auto", whiteSpace: "nowrap" }, table: { tableLayout: "fixed" } }); export default function SortableTable({ data, className }) { console.log(data) const classes = useStyles(); const columns = Object.keys(data[0]); const [state, setState] = useState({ rows: data, order: "desc", key: columns[0] }); function handleClickSortColumn(column) { const isDesc = column === state.key && state.order === "desc"; const nextOrder = isDesc ? "asc" : "desc"; const sortRule = { asc: [1, -1], desc: [-1, 1] }; const sortedRows = state.rows.slice().sort((a, b) => { if (a[column] > b[column]) { return sortRule[nextOrder][0]; } else if (a[column] < b[column]) { return sortRule[nextOrder][1]; } else { return 0; } }); setState({ rows: sortedRows, order: nextOrder, key: column }); } return ( <div className={className}> <Paper className={classes.root}> <Table size="medium" className={classes.table}> <TableHead> <TableRow> {columns.map((column, colIndex) => ( <TableCell align={isNaN(state.rows[0][column]) ? "left" : "right"} key={`table-header-col-${colIndex}`} sortDirection={state.key === column ? state.order : false} > <TableSortLabel active={state.key === column} direction={state.order} onClick={() => handleClickSortColumn(column)} > {column} </TableSortLabel> </TableCell> ))} </TableRow> </TableHead> <TableBody> {state.rows.map((row, rowIndex) => ( <TableRow hover key={`table-row-row-${rowIndex}`}> {Object.keys(row).map((key, colIndex) => ( <TableCell align={isNaN(row[key]) ? "left" : "right"} key={`table-row-${rowIndex}-col-${colIndex}`} > {row[key]} </TableCell> ))} </TableRow> ))} </TableBody> </Table> </Paper> </div> ); } SortableTable.propTypes = { data: PropTypes.arrayOf(PropTypes.object).isRequired, className: PropTypes.string, };

補足情報(FW/ツールのバージョンなど)

よろしくお願い致します。

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

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

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

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

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

hoshi-takanori

2020/03/10 01:01

一度目の表示はまだ API から結果が帰ってきてないためで、「ロード中」みたいなのを表示するのがいいと思います。 ログが無限に出てくるのは、表示内容を更新するたびに App 関数が実行されて fetch が呼ばれるためで、API を繰り返し呼びすぎてますね。useEffect を使わないとやばいです。
guest

回答1

0

問題
・fetch APIは非同期通信なので、そもそもレンダリングが一度された後に呼び出されます。
->そのためレンダリングされた段階ではstateの値は初期値のままです。
・useStateは、Stateの値が変更されると再レンダリングされます。
->つまり質問者様が今されているのは、レンダリング(state初期値)-> fetch API(値取得) -> setState(値変更)-> 値変更がされたため再レンダリング ...と無限ループに陥っています。

・対策としてはまずLoading画面をレンダリングさせ、その間にAPIから値を取ってきて、通信完了したら値を表示するなどレンダリングさせる戦法が挙げられます。

外部APIから値を取ってくるときは、React Hooksでは主にuseEffectを使います。 クラスcomponentでいうcomponentDidMount()あたりのタイミングで呼び出されます。

useEffect(() => { //fetchとかの非同期処理の内容 }, [])

第二引数に[]の空配列を入れると初回のレンダリング後のみuseEffectが呼び出されるので、無限ループにならなくなります。詳しくはuseEffectとかhooksの公式サイトとかQiitaを見てもらえるとわかりやすい記事があります。

だいたいこんな感じ。

const [loading, setLoading] = useState(false) useEffect(() => { setLoading(true) fetch(url).then((r) => r.json()).then((data) => { setState({ symbol: data.symbol, name: data.companyName, price: data.iexRealtimePrice, share: 10, totalValue: 200 }); setLoading(false) }); },[]) if (loading) { return <p> 読み込み中 </p> } return ( //ここにコンポーネント )

また、React Hooksを使って、APIから取ってきた値をどの様に保存しておけばいいのかも分かっておりません。useStateでいくつか配列を用意しておいた場合、どの様に値を追加していけばいいのでしょうか?

基本はsetStateで現在のstateから+1, +2、、、とすれば値の変更はできます。配列の追加も同様にできると思います。stateは更新されるたびレンダリングされます。onClickなどで実装するのが一般的ですね。

シンプルなアプリならuseStateで十分ですが、コンポーネントが多くなってきたり、情報を一元管理するならReduxの学習をおすすめします。(Reduxはややこしいので今回はそこまで行かなくてもいいとは思いますが)

(追記)
上記のやり方でstateは取れますが、useEffect内でuseStateを書くとESLintで(エラーではなく)警告が出る場合があります。気になる場合は別の方法を試すことをお勧めしますが、私もReact歴2ヶ月とかなのでそこまでは把握しておりません。

投稿2020/03/11 05:58

編集2020/03/11 06:01
HayateIshida

総合スコア10

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問