react-railsに、とりあえずReact Context APIを適用してみた

# はじめに
Reactを学習し、プロジェクトの実装に入れてみて数ヶ月が経ちました。
Reactの実装には慣れたものの、やはり直面するのはStateの管理や、props drilling問題。
何とか解消できないものかと、Reduxの導入を検討するも、難解でかつ、すでに作成されたプロジェクトのコードにうまく入れられず...
他に何かいい方法はないかと、いろいろ調べてみた結果、
**React Context API**
というのがあり、それを使えばバケツリレーが解消されるとのこと！
早速調査して使ってみました！

# React Context APIとは？
Reactは16.3以降、それまで実験的な扱いだったContext APIを刷新し、新しく生まれ変わらせたようです。
具体的には、Contextを使用することにより、バケツリレーでデータを渡すことなく、データをコンポーネント間で共有できるというものでした。
こちらの記事が詳しいので、掲載させて頂きます。
[React v16で実装された new Context APIを使って、Reduxへ別れを告げる](https://qiita.com/kaba/items/50126e874b24ff984471)

# やってみよう
以前、こんな感じのタスクリストを作りました。
[react-railsを使って、簡単なデータ表示＋検索のサンプルを作ってみる](https://qiita.com/laser_beam/items/97483176c712e408907f)
データの受け渡しを、こんな感じでやってました。

```JavaScript:Task.js
  render() {
    return (
      <div>
        <h1>タスクリスト</h1>
        <FilterForm search={(name) => this.searchByName(name)} />
        <TaskList tasks={this.state.tasks} />
      </div>
    )
  }
```

検索を行うコンポーネントに、
入力内容で絞り込みを行うというロジック（=searchByName）を、
タスクリストに、
絞り込んだ結果のタスクリスト（railsから渡された、taskオブジェクトの配列です）
を、それぞれpropsで渡しています。
こちらのサンプルを、React Context APIで書き直します。

## React Context APIのポイント

ポイントとなるのは、ContextAPIを構成する、3つの要素です。
１、Context：コンポーネント間で共有したいオブジェクトを指します。値だけでなく、関数を持ったりもできます。
２、Provider：Contextの渡し手です。
３、Consumer：Contextの受け取り手です。
要するに、ProviderとConsumerという2つを使って、Contextの受け渡しをすることで、propsなしで値や関数を共有できるということです。

それぞれ見ていきましょう。

### Contextの作成

```JavaScript:
import { createContext } from 'react';

const TaskContext = createContext();
```
ここでContextを作成してます。この時点で、このコンテキストに対するProviderとConsumerが使えるようになります。
また、Contextを複数個作ることも可能なようです。

### Providerの定義

```JavaScript:
render() {
    return (
      <TaskContext.Provider value={{
        tasks: this.state.tasks,
        search: (name) => this.searchByName(name)
      }}>
        <div>
          <h1>タスクリスト</h1>
          <FilterForm />
          <TaskList />
        </div>
      </TaskContext.Provider>
    )
  }
```

ここで、Providerで何を渡すか、value=というところで定義しています。tasksはタスク一覧（オブジェクトの配列）、searchは検索用の関数です。
このように、<TaskContext.Provider>というタグで囲ってあげると、囲われているコンポーネント全てで、valueの値を取ることができます。

### Consumerの定義

```JavaScript:
  render() {
    return (
      <TaskContext.Consumer>
        {context => {
            return(
              <div className="entry">
                <fieldset>
                  <legend>検索</legend>
                  <div>名前で検索: <input type="text" value={this.state.name} name="name" onChange={(e) => this.onChangeName(e)} placeholder="例：買い物" /> </div>
                  <div> <input type="submit" value="検索" onClick={() => {context.search(this.state.name)}} /> </div>
                </fieldset>
              </div>
            )
          }
        }
      </TaskContext.Consumer>
    )
  }
}
```

```JavaScript:
const TaskList = () => (
  <TaskContext.Consumer>
    {
      ({ tasks }) => {
        return (
          <React.Fragment>
            <div>
              <table className="task">
                <thead data-type="ok">
                  <tr><th>名前</th><th>詳細</th><th>開始日</th><th>終了日</th></tr>
                </thead>
                <tbody>
                  {tasks.map((task) =>
                    <TaskItem task={task} key={task.id} />)}
                </tbody>
              </table>
            </div>
          </React.Fragment>
        )
      }
    }
  </TaskContext.Consumer>
)
```

この2つのコンポーネントで、Consumerを使用して、Providerからの値を受け取っています。
今回はpropsを使わないサンプルでしたが、propsも使えるようです。

### コード全体

最後に、コード全体を載せておきます。

```JavaScript:Task_ContentAPI.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { createContext } from 'react';

const TaskContext = createContext();

//メインのコンポーネント
class Task_ContextAPI extends Component {
  //コンストラクタ。
  //このコンポーネントのstateには絞り込み前のタスク一覧と、絞り込み後のタスク一覧をそれぞれ用意する
  constructor(props) {
    super(props)
    this.state = {
      initialTasks: this.props.tasks, searchedTasks: [], tasks: [],
    }
  }

  //ブラウザロード時の処理。
  componentDidMount() {
    this.setState({ tasks: this.state.initialTasks.slice(0, 20) });
    this.setState({ searchedTasks: this.state.initialTasks });
  }

  //検索のメソッドをここで用意しておく
  searchByName(name) {
    const result = this.state.initialTasks.filter((task) => {
      return task.name.toLowerCase().search(name.toLowerCase()) !== -1;
    })
    this.setState({ searchedTasks: result });
    this.setState({ tasks: result.slice(0, this.state.list_count) });
  }

  /* 
    ページ全体のrenderメソッド。
    Providerで渡すものを定義することにより、propsの値がないようにした
  */
  render() {
    return (
      <TaskContext.Provider value={{
        tasks: this.state.tasks,
        search: (name) => this.searchByName(name)
      }}>
        <div>
          <h1>タスクリスト</h1>
          <FilterForm />
          <TaskList />
        </div>
      </TaskContext.Provider>
    )
  }
}

//検索フォームのコンポーネント
class FilterForm extends Component {

  //コンストラクタ。ここでは、検索値nameをstateとして持っておく
  constructor(props) {
    super(props)
    this.state = { name: '' }
  }

  //検索のテキストボックスの中身が変更された時の処理。
  //stateに検索値を挿入しておく
  onChangeName(event) {
    this.setState({ name: event.target.value })
  }

  //検索フォームのrenderメソッド。
  //検索用のメソッドをProviderから受け取る。
  render() {
    return (
      <TaskContext.Consumer>
        {context => {
            return(
              <div className="entry">
                <fieldset>
                  <legend>検索</legend>
                  <div>名前で検索: <input type="text" value={this.state.name} name="name" onChange={(e) => this.onChangeName(e)} placeholder="例：買い物" /> </div>
                  <div> <input type="submit" value="検索" onClick={() => {context.search(this.state.name)}} /> </div>
                </fieldset>
              </div>
            )
          }
        }
      </TaskContext.Consumer>
    )
  }
}

//タスク一覧についてのコンポーネント。
//タスクリストをProviderから受け取る。
const TaskList = () => (
  <TaskContext.Consumer>
    {
      ({ tasks }) => {
        return (
          <React.Fragment>
            <div>
              <table className="task">
                <thead data-type="ok">
                  <tr><th>名前</th><th>詳細</th><th>開始日</th><th>終了日</th></tr>
                </thead>
                <tbody>
                  {tasks.map((task) =>
                    <TaskItem task={task} key={task.id} />)}
                </tbody>
              </table>
            </div>
          </React.Fragment>
        )
      }
    }
  </TaskContext.Consumer>
)

//タスクの1つの行を表すコンポーネント。
//上と同様関数コンポーネント。
const TaskItem = (props) => {
  //受け取ったタスクのオブジェクトの値を、それぞれ行のセルに挿入。
  const { name, description, start_date, end_date } = props.task
  return (
    <tr>
      <td>{name}</td>
      <td>{description}</td>
      <td>{start_date}</td>
      <td>{end_date}</td>
    </tr>
  )
}
//TaskItemコンポーネントが受け取るpropsを定義。
//ここではタスクオブジェクト。
TaskItem.propTypes = {
  task: PropTypes.object.isRequired
}

export default Task_ContextAPI

```

# 終わりに
もっと深い階層のサンプルであれば、バケツリレーをしない様がお見せできたかと思いますが、
時間の都合上、過去の記事のものを書き換えるに留まり...
しかもざっと学んで書き換えてみた、という程度なのでコード自体も汚いかと...
ですが、それでも誰かのためになれたら幸いです。

ここまで読んで頂き、ありがとうございました！