日頃の学習の成果や困ったこと、
新発見などを投稿します。

Reactでヌメロンゲームを作った話(stateの更新)

TypeScript
JavaScript

投稿日時:

2021-12-11

まずヌメロンゲームとは

答えの数字(今回は3桁)が用意されていて自分が入力した数字によってbite(数字と桁が一致),eat(数字が一致)が表示されてそのヒントをもとに完全一致を目指すゲーム。

state管理するもの

Reactでは状態が変化するものをstateで管理するので、まずそれを考える。

  • 入力された値
  • Bite数
  • Eat数
  • 結果(これはstateで管理する必要ないかも)


早速コード

import React, { useState } from "react";	
import "./styles.css";

const answer: string[] = ["4", "5", "6"];
		
export default function App() {		

  const [num, setNum] = useState<string>("");
  const [biteCount, setBiteCount] = useState(0);
  const [eatCount, setEatCount] = useState(0);
  const [result, setResult] = useState("");
		
  const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNum(e.target.value);
  };

  const clickHandler = () => {
    const numArray = num.split("");
	
    setBiteCount(0);
    setEatCount(0);
	
    numArray.forEach((elem, index) => {
      if (answer.includes(elem)) {
        if (answer[index] === elem) {
          setBiteCount((prev) => prev + 1);
        } else {
          setEatCount((prev) => prev + 1);
        }
      }
    });
    setResult(`${biteCount}BITE${eatCount}EAT`);
  };
	
  return (
    <div className="App">
      <input onChange={changeHandler} type="number" value={num} />
      <button onClick={clickHandler}>判定</button>
      {result && <p>{result}</p>}
    </div>
  );
}


今回は答えを定数[4,5,6]にしている。
入力された値をひとつづつ区切って配列にしてforEachで回してBite,Eat数をカウントしている。

実はこのコード欠陥がある。
クリックした際に一回目のクリックで前回のカウント数を表示してしまうのだ。
つまり2回クリックしないと正常に動作しないという事。
なぜそんなことが起こるかというとクリックした際にbiteCount,eatCountを更新する処理を行っているのだが、同じ関数内でsetResultを使ってbite,eatを更新しているので、実はstateの状態は更新される前のbite,eatつまり一つ前の状態になっている。

onblurを使う

onblurとはフォーカスが外れた時に発生するイベント。
つまりinputからフォーカスが外れた時にbite,eatを更新する処理を走らせればよい。

状態が更新されるタイミングは以下の通りになる。

  • inputの変更を検知した時=>numの更新
  • inputからフォーカスが外れた時=>bite,eatの更新
  • 判定ボタンを押した時=>resultの更新


改善後のコード

import React, { useState } from "react";
import "./styles.css";


const answer: string[] = ["4", "5", "6"];


export default function App() {
  const [num, setNum] = useState<string>("");
  const [biteCount, setBiteCount] = useState(0);
  const [eatCount, setEatCount] = useState(0);
  const [result, setResult] = useState("");
  const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNum(e.target.value);
  };


  const blurHandler = () => {
    setBiteCount(0);
    setEatCount(0);
    const numArray = num.split("");
    numArray.forEach( (elem, index) => {
      if (answer.includes(elem)) {
        if (answer[index] === elem) {
          setBiteCount((prev) => prev + 1);
        } else {
          setEatCount((prev) => prev + 1);
        }
      }
    });
  };


  const clickHandler = () => {
    setResult(`${biteCount}BITE${eatCount}EAT`);
  };


  return (
    <div className="App">
      <input
        onChange={changeHandler}
        onBlur={blurHandler}
        type="number"
        value={num}
      />
      <button onClick={clickHandler}>判定</button>
      {result && <p>{result}</p>}
    </div>
  );
}


こんな感じになった。onblurは意外と良いかもしれん。

結論

結果をstateで管理しなければこんな面倒なことにはならん。