hamao
@hamao

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

useEffectで依存関係を解消するためにsetStateを使っていい?

useEffectで依存関係を解消するためにsetStateを使っていい?

reactでタイピングゲームを作っています
wordsを被りなくランダムに表示するために下のコードのような実装を行っています.

使用環境

package.json
"dependencies": {
    "next": "13.1.1",
    "react": "18.2.0",
    "react-dom": "18.2.0"
}

該当するソースコード

const [words, setWords] = useState<Words[]>([...]) 
const [word, setWord] = useState<Word>({...})
const [index, setIndex] = useState<number>(0)

useEffect(() => {setWord(words[index])}, [words, index])

このような実装にすると, wordsとindexの両方の変更に反応してしまいます. どうにかして, missing dependencyを起こさずに, indexの変更のみに反応するようにしたいです.

自分で試したこと

useEffect(() => {
    setWords((words) => {
        setWord(words[index]);
        return words;
    });
}, [index]);

このように, wordsを依存関係に含めないようにするために, setWordsを使ってその中でwordsを参照するようにするのは正しい実装ですか? 可読性が悪い気がするし, あまり良い実装に思えません.
現在はonKeydownでindexを更新するような感じなのですが, それをやめて直接setWordをするような形にしたりするべきでしょうか?

この記事を見たのですが, 英語なのもありあまり理解できませんでした.

今回のアプリに限らず, useEffectの中でsetStateをこのような使い方をするのは良いのでしょうか?
よろしくお願いします.

0

3Answer

どうにかして, missing dependencyを起こさずに, indexの変更のみに反応するようにしたいです.

linterのためにわざわざそんなコードを書くくらいなら個人的には単純にlinterを黙らせます.

そもそもの設計として,wordsを保持するコンポーネントは分けるべきなのかもしれません.(おそらくタイピングゲームのプレイ中に,単語リストを置き換える機会がそうそうない)
wordsの型がWords[]になってるのが間違いでないのなら,相当設計をとっ散らかしてる可能性がありますので,フローを見直してみてください.

1Like

Comments

  1. @hamao

    Questioner

    ありがとうございます.
    型情報はWords[]はミスでWord[]が正しいです.
    プレイ画面のヘッダーにステージ選択のセレクトを設置していて, そのonChangeで単語範囲指定用のqueryが変わるため, query変更時にsetWordsが発火するような形になっています. その際に, 一瞬画面がチラつくので, wordsの変更は無視したいと考えていました. 本文には書いていないですが, useEffectでwordsが変わった時は, indexが0になるようにしています.
    linterはどの程度無視しても良いものなのでしょうか?
  2. wordsに自ら干渉する必要が無いならwordsは親コンポーネントに持っておいて,propsで渡されてくる方が良いような印象を受けます.
    少なくとも描画内容に関してこのコンポーネントが責任を負い過ぎているようです.

    eslintはコーディング規約導入を支援するためのツールなので,linterをどの程度無視するかという質問自体ナンセンスです.私からは都合のいいように.eslintrcを設定してねとしか言えません.

    追記:なんかrefで持てとか言っている人いますけど描画内容に影響する情報にrefは使わないでください.それはReactのライフサイクルに反する行為です.
  3. @hamao

    Questioner

    ありがとうございます

こんにちは。

const [words, setWords] = useState<Word[]>([...]) 

const words = useRef<Word[]>([...])

のように定義するのはいかがでしょうか。
こうすればuseEffectの依存配列からwordsをなくすことが可能になります。
質問文の内容を拝見しているとおそらくwordsをstateで管理する必要はなさそうでしたのでこちらの対応方法を提案させていただきました。

0Like

仕様の詳細にも依りますが、indexが決まればそれを使ってwordも一意に決まるということであれば、そもそもindexwordの2つのstateを持つ必要はなさそうですね

その場合具体的には、

const [words, setWords] = useState<Words[]>([...]) 
const [index, setIndex] = useState<number>(0)
const word = words[index]

とするだけでよく、useEffectは不要になります。


また以下はあくまで参考までにですが、wordをstateとしたままにするのであれば、単に以下とするのが普通だと思います

useEffect(() => {
    setWord(words[index]);
}, [index]);
0Like

Comments

  1. @hamao

    Questioner

    ありがとうございます

Your answer might help someone💌