【Reactポモドーロアプリ】inputの入力値を別のcomponentで使用する

以前、JavaScriptでポモドーロタイマーを作成しましたが、現在そのアプリをReactへリプレイスしています。

React版でもユーザーが作業時間や休憩時間を設定できる機能を追加したのですが
実装までに苦戦したので、記録として残しておきます。

ただ、今回紹介する方法は師匠からのレビュー前のコードになります。
初心者の私が考えた処理手順になるので、もっといい方法はあると思います....
記録として残しておくという意味合いが強いので、その辺りご了承頂けますと幸いです。

※ポモドーロサイクルの実装もさらに苦戦したので、今度どこかのタイミングで記事にしたいと思います。順番が逆転してしまい、ごめんなさい....

ディレクトリ構成

f:id:hnm-n-1029:20200824192648p:plain

現在のディレクトリ構成はこのようになっております。

赤枠に注目すると分かるのですが、今回はPresentational Component(componentsフォルダ) とContainer component(containersフォルダ)に役割を分離させています。
Presentational Component:View担当
Container component:ロジック担当

React/Redux環境ではプラクティスの一つらしいです。
こちらの記事が分かりやすいと思います!
[redux] Presentational / Container componentの分離 - react-redux.connect()のつかいかた - Qiita

それぞれ関心事をきちんと分離することでコンポーネントの再利用性を高めることができます。

ポモドーロタイマーはそこまで大きいアプリではないですが、
関心事を分けることでコードがスッキリして見やすいので分けることにしました。

実現したいこと

f:id:hnm-n-1029:20200824194817p:plain

青枠内の作業時間に数字を入力後「OK」を押下と同時にその値が赤枠内に残り時間として反映される、ということ。

それぞれのviewは画像に記載した通り以下のPresentational componentが担当しています。
カウントダウンエリア(赤枠):CountDown.jsx
ユーザー入力エリア(青枠):userInput.jsx


作業時間の値は親コンポーネントである、App.jsxのstateで管理しようと考えました。
そして、赤枠内に表示されている残り時間はCountDown.jsxがStateで管理しているので
CountDown.jsxのStateの値をApp.jsxのstateの値で更新する、という流れです。

まとめると、値の流れとしては以下のようになります。

userInput.jsx→App.jsx→CountDown.jsx

①userInputで入力した値をAppに渡す
②AppのStateを更新
③更新したStateの値をCountDownへ渡す
④CountDownで受け取ったpropsでStateを更新させる

実際に書いたコード

順を追って説明していきます。

①userInput(view担当):onChange、onClickにコールバック関数を設定

青枠のviewを担当しているuserInput.jsxのコードは以下になります。
※作業時間のinputだけ抜粋しています。Material UIを導入していますが、Material UIについての詳しい説明は今回は省きます。

//components > userInput.jsx

const UserInputComponent = ({
  //Containers > userInput.jsxからpropsを受け取る
  handleInputWorkTime, inputWorkTime, callInputValueSet
}) => {
  return (
    <form className={classes.root} noValidate autoComplete="off">
      <StyledInputField>
        <StyledTextField id="standard-basic" 
          onChange={handleInputWorkTime} //inputの入力値を取得する関数
          value={inputWorkTime} //ユーザー側から設定を変更できるように
          label="作業時間"
        />
      </StyledInputField>
      <StyledButton variant="contained"
        type="submit"
        onClick={callInputValueSet} //App.jsxのStateの値を更新させる関数を呼ぶ関数
      >OK</StyledButton>
    </form>
  );
};

export default UserInputComponent;

まずは、以下2つの関数を記述。どちらもこれからcontainers>UserInput.jsxで作成する関数です。

①inputの入力値を取得する関数
②App.jsxのStateの値を更新させる関数を呼ぶ関数

②UserInput(ロジック担当):onChange、onClickに設定したコールバック関数のプログラムを定義

//components > UserInput.jsx

const UserInputContainer = (props) => {
  const[inputWorkTime, setInputWorkTime] = useState(25);
  
  const handleInputWorkTime = (e) => {
    //入力値を取得しState(UserInputContainerが持つstate)を更新
    setInputWorkTime(e.target.value); 
  }

  const callInputValueSet = (e) => {
    e.preventDefault(); //再レンダリングをキャンセル
    //AppのStateを更新する関数へ入力値を引数として渡す
    props.inputValueSet(inputWorkTime);
   }

   return (
       <>
  //UserInputComponent へpropsを渡す
         <UserInputComponent 
           handleInputWorkTime={handleInputWorkTime}
         inputWorkTime={inputWorkTime}
         callInputValueSet={callInputValueSet}
      />
    </>
  );
};

③Appで関数の定義と、Countdown(ロジック担当)へpropsを渡す

//App.jsx

const App = () => {
  const[workTime, setWorkTime] = useState(25);

  const inputValueSet = (inputWorkTime) => {
    setWorkTime(inputWorkTime);
  }

return (
    <div className="App">
      <GlobalStyle />
      <Header />
      <div className="module-spacer" />
      <CountdownContainer
        workTime={workTime}  //入力値をpropsとして渡す
      />
      <div className="module-spacer" />
      <InputTimeDisplay/>
      <div className="module-spacer" />
      <UserInputContainer 
        inputValueSet={inputValueSet} //関数をpropsとして渡す
      />
    </div>
  );
}

export default App;

④Countdown(ロジック担当):useEffectの定義

//Containers > UserInput.jsx

const CountdownContainer = (props) => {
//残り時間はleftSecで管理
let[leftSec, setLeftSec] = useState(props.workTime*60);

~中略~

//workTimeが更新されたらleftSecが更新される
  useEffect(() => {
    setLeftSec(props.workTime*60); 
  }, [props.workTime])

  return (
    <CountdownComponent
      workTime={props.workTime} //
    />
  );
}

⑤Countdown(View担当):受け取ったpropsを表示

const CountdownComponent = ({leftSec}) => {
  return (
    <>
      <StyledTimeDesplay>{secToMMSS(leftSec)}</StyledTimeDesplay>
      <div className="module-spacer" />
      <div>
       <StyledButton variant="contained" onClick={handleStart} disabled={active}>START</StyledButton>
       <StyledButton variant="contained" onClick={handleStop} disabled={!active}>STOP</StyledButton>
       <StyledButton variant="contained" onClick={handleReset} disabled={!active}>RESET</StyledButton>
      </div>
    </>
  );
}

export default CountdownComponent

※secToMMSSは秒を【○○:○○】の表示形式に変換する関数です。


これで、Inputに入力した値が、Countdownのviewに反映されました!

今回は以上です!


↓↓私の師匠、もりけんさんの武骨日記。問題集、要チェック

kenjimorita.jp