React小ネタ集 - stateでコンポーネントを複製, forEachで繰り返しrenderする


趣旨

同じようなcomponentをn個レンダリングしたい & 個々の細部はちょっとだけ変えたいという時に使える小ネタ。開発中に使えると思ったことをメモした。

作ったもの

https://codesandbox.io/s/qkvv35mn26
labelとテキストボックスがひとまとまりになった子コンポーネントを生成して、「挨拶する」ボタンをクリックすると個々の入力内容をアラートで表示するだけのシンプルなやつ。React-Bootstrapで簡単にスタイル付けもしている。

TL;DR

  • state.languageに新たな要素を追加すれば勝手にrenderされるコンポーネントが増える。
  • 合わせてshowFieldとinputTextにも要素を追加すれば何ヶ国語でも挨拶できるよ
  • codesandbox上で自由に試して見てください

コンポーネントの構造

  • App (親コンポーネント)
  • MessageBox (子コンポーネント)

stateで制御しているもの

showField (Array)

  • 各コンポーネントのうち、テキストエリアの表示状態を管理。
  • 青文字のリンクをクリックするとハンドラが表示状態をトグルしてくれる。

language (Array)

  • [言語コード, 言語名]が組みになった要素を格納した配列。

inputText (Object)

  • 各テキストエリアの入力内容

各メソッドについて

showText

App.js
showText(e, langCode) {
    let greeting = e.target.value;
    if (this.state.inputText !== "") {
      this.setState({
        inputText: Object.assign({}, this.state.inputText, {
          [langCode]: greeting
        }),
      })
    }
  }

テキストエリアの入力内容に変更が起こると発火する。
入力したテキストをstateに格納してそのまま反映しているが、this.state.inputTextは各言語コードがキーとなっているのでそれを上書きしないよう、引数でイベントと言語コードを同時に受け取っている。Object.assignの第二引数では既存のstateを受け取り、各言語コードをキーとして入力内容と合成し再びオブジェクトに結合してsetStateしている。

Object.assign()はReduxでもよく使われるが、素のReactでもこういった場面で使える。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

ちなみに普段通りにsetStateしようとすると一番新たに入力した言語のテキストしかstateにセットされなくなるので注意。

this.setState({inputText: {[langCode]: greeting}}),

handleTextField

App.js
handleTextField(e, number) {
    e.preventDefault();
    let showFieldState = this.state.showField;
    showFieldState[number] = !this.state.showField[number];
    this.setState({ showField: showFieldState });
  }

「○○語で挨拶しましょう」というリンクをクリックすると発火する。
各コンポーネントはthis.state.showFieldの配列で表示状態を管理しているので、n番目のコンポーネントの状態だけをトグルするように記述している。

render

App.js
render() {
    const boxes = []
    this.state.language.forEach((value, index) => {
      boxes.push(<MessageBox
                  props= { this.state }
                  showText= { this.showText }
                  handler= { this.handleTextField }
                  key= { index }
                  showNum= { index } />)
    })

Array.forEach()を使えばlanguageに入っている要素数だけコンポーネントをrenderできる。
ただ、return()句のなかでやろうとすると即時関数でラップしたりする必要があり、色々と面倒なので、returnの前で空配列を定義 -> forEachでコンポーネントを詰め詰め という手順でやっておいた方がラク。
keyに配列の添字を渡してやらないと各コンポーネントを識別できなくなりWarningが出るので注意。
showNumにも同じく配列の添字を渡してやることでthis.state.languageの内容を子コンポーネント側で読み取って反映させられる。