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

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

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

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

Q&A

解決済

2回答

3386閲覧

useStateでstateが更新されない

react.js

総合スコア2

React.js

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

0グッド

1クリップ

投稿2021/09/06 13:13

編集2021/09/06 13:28

実現したいこと

概要

useState()でstateの値が更新されないので、更新したい。

詳細

useStateでconst [isStart, setIsStart] = useState(false);を定義しています。
またisStartを更新する関数として、以下二つの関数を定義しています。

js

1 const startGame = () => { 2 setIsStart(true); 3 }; 4 const endGame = () => { 5 setIsStart(false); 6 };

私の想定では、スペースキーをクリックすれば、isStartがtrueに、escキーをクリックすれば、isStartがfalseになると思っているのですが、
スペースキーをクリックしても、case 32のconsole.log(isStart)ではfalseと出力されます。
その後、escキーを押してもisStartが更新されていないか、ifブロックに入ってくれません。

このコードでは、stateは更新されないのでしょうか?

発生している問題

useState()でstateの値が更新されない。

該当のソースコード

js

1import React, { useCallback, useEffect, useState } from 'react'; 2 3const Typing = () => { 4 const [isStart, setIsStart] = useState(false); 5 const startGame = () => { 6 setIsStart(true); 7 }; 8 const endGame = () => { 9 setIsStart(false); 10 }; 11 const onClickKey = useCallback((event) => { 12 switch (event.which) { 13 // スペースキーコードは32。 14 case 32: 15 console.log(isStart) // falseと出る 16 startGame(); 17 break; 18 // エスケープキーコードは243。 19 case 27: 20 if (isStart) { 21 console.log(isStart) // isStartがずっとfalseのためこのブロックに入らない 22 endGame(); 23 }; 24 break; 25 default: 26 break; 27 } 28 }, [isStart]); 29 30 useEffect(() => { 31 document.addEventListener("keydown", onClickKey, false); 32 }, []); 33 34 return ( 35 <div className="typing"> 36 <Timer 37 isStart={ isStart } 38 /> 39 </div> 40 ) 41} 42 43export default Typing

試したこと

useState()でstateが更新されないことをググってみたが、パッとした記事が見つかりませんでした。

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

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

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

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

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

guest

回答2

0

ベストアンサー

結論

keyDownEventの登録をonClickKeyの更新に依存させます。

js

1 useEffect(() => { 2 document.addEventListener("keydown", onClickKey, false); 3 return () => document.removeEventListener("keydown", onClickKey); 4 }, [onClickKey]);

説明

私の理解が間違っている可能性がありますが、簡単に説明します。
useStateによって使用できるisStartは(おそらく)レンダリングごとに別の値参照を持ちます。
setIsStartは同じ関数参照を持ちます。

document.addEventListener("keydown", onClickKey, false)で登録したonClickKey関数は初回レンダリングのisStartのみを参照し、それ以降のレンダリングで更新されたisStartを捕捉することはできません。setIsStartによって更新だけは出来ますが、更新された値による条件分岐は行われず、endGameは永久に実行されることはありません。

結論で示したコードでは、isStartに依存するonClickKeyに依存してイベントの再登録を行っています。これにより、isStartが更新された時に新しい参照を使用して関数が作成されるため、新しい値での条件分岐(など)が可能になります。

投稿2021/09/06 17:43

編集2021/09/06 17:51
k4a

総合スコア983

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

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

k4a

2021/09/06 17:50

Reactで`addEventListner`などの手続き的なイベント付与は(私も使いますが)おそらく邪道なので、通常であればあまり問題にならないのかな、と思います。 宣言的なイベント付与`onClick={hoge}`はレンダリングの度に新しく登録されるので、手続き的な書き方でもこれに則って回答の様に毎回イベントを登録し直すのが良さそうです。 (私もこの回答で初めて考えたので本当に全くの検討違いの可能性はあります)
react.js

2021/09/07 02:34

ご指摘の通りで、やりたいことは実現できました。ありがとうございました。 addEventListnerはReactでは使わない方が良いみたいですね。 より良い方法をもう少し探してみます。
guest

0

React本家ドキュメントのHandling Eventsの中に

When using React, you generally don’t need to call addEventListener to add listeners to a DOM element after it is created. Instead, just provide a listener when the element is initially rendered.

と書いてある。これを破って、Reactを使っている中で addEventListener すると、(予想外の)意図しないことが起こると思っていたほうがよい。実際、質問にあるコードのまま動かすと、

React Hook useEffect has a missing dependency: 'onClickKey'. Either include it or remove the dependency array react-hooks/exhaustive-deps

という警告が表示されるので、この警告に従って、useEffect の dependency(第二引数)の配列に onClickKey を追加すると警告は消えるが、スペースキーとエスケープキーを交互に何度も押すと、そのたびに新しいonClickKeyがdocumentにaddEventListenerされてしまうので、それまでに追加されていた複数のonClickKeyの各々によって console.log(isStart)されてしまって、意図していた結果(=スペースキーまたはエスケープキーの一回の押下によって、console.log(isStart)が一回だけ実行されて、isStartが一度だけ出力される。)とは違った結果になってしまう。

投稿2021/09/06 21:36

編集2021/09/06 21:37
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

react.js

2021/09/07 02:36

ご回答ありがとうございます。 addEventListenerはReactでは使用しない方がいいようですね。 とすると、jsxの中でonKeyPressに関数を渡すやり方がセオリーなんでしょうか?
退会済みユーザー

退会済みユーザー

2021/09/08 01:36 編集

そうですね。 キーボード入力を受けつける <div> だったりのonKeyPress propにキー押下時のハンドラを設定するのがよいかと思います。つまり const handleKeyPress = useCallback((event) => { ・・・ }); というハンドラを作っておいて <div onKeyPress={handleKeyPress} ・・・ とするということです。(ちなみに、onXXX prop に設定するハンドラの変数名は、handleXXXのように、 handle をプレフィクスにする慣例がしばしば見られます) Reactのコンポーネントの中で、document. addEventListener するコードを書きそうになったら、 「怪しいコードを書こうとしているな・・・」と思ったほうがよいかもしれません。
react.js

2021/09/07 11:40

理解できました。 Reactのセオリーが知れて良かったです。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問