Reduxいらないかも？！GraphQL + ReactでReduxを使わずにいい感じに実装した

## はじめに

RailsとReact、GraphQLを使って簡単なアプリを作りたく、勉強がてらRails + React + GraphQL + Apolloで簡単なアプリを作ってみました。
その際に、GraphQL使うならReduxを使うメリットってないんじゃないかなと思い、Reduxを使わずにいい感じに実装できないかなと試行錯誤したことを記事にしてみようと思います。

実際のアプリは[Takumi0901/rails-react-sample](https://github.com/Takumi0901/rails-react-sample)にあります。
もしstarとかforkとかissueもらえると嬉しいです。よろしくお願いします^ ^


## Atomic Designを使ったディレクトリ構成

## Atomic Designとは
- [Atomic Design | Brad Frost](http://bradfrost.com/blog/post/atomic-web-design/)
- [Atomic Design を分かったつもりになる | DeNA DESIGN BLOG](http://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/)

Atomic Designでは5つのステージに分けて管理します。

- Atoms(アトム) – 原子
- Molecules(モルキュール) – 分子
- Organisms(オルガニズム) – 有機体
- Templates(テンプレート) – テンプレート
- Pages(ページ) – ページ

> これらのステージは上から下に行くにつれて、粒度は大きくなり、抽象度は下がります。 抽象度の高いコンポーネント（たとえば原子）を合体させて、繰り返し可能なコンポーネント（分子）やテンプレートを構築できるようにデザインを考えていきます。 ページをデザインするのではなく、コンポーネントで構成するデザインシステムです。

ってことで、こんな感じのディレクトリ構成にしてみました。

```
├── actions
├── components
│   ├── atoms
│   ├── molecules
│   ├── organisms
│   └── templates
├── containers
│   ├── hoc
│   └── pages
├── helper
├── router
└── startup
```

ざっくり一つずつ説明をすると

### actions
ここではGraphQLのQueryをまとめたり、共通して使えるstateを変数にまとめています。

``` Books.js
import gql from "graphql-tag"

export const FETCH_ALL_BOOKS_QUERY = gql`
  query {
    books {
      id
      name
      picture
    }
  }
`
```

``` Fetch.js
export const FETCH_INITIAL_STATE = {
  succeeded: false,
  deleted: false,
  errors: {}
}

export const FETCH_SUCCEEDED_STATE = {
  succeeded: true,
  deleted: false,
  errors: {}
}

export const FETCH_DELETED_STATE = {
  succeeded: false,
  deleted: true,
  errors: {}
}

export const FETCH_IS_ERROR_STATE = (errors = {}) => {
  return ({
    succeeded: false,
    deleted: false,
    errors: errors
  })
}
```

### componentsとcontainers
この中でAtomic Designの概念でcomponentを管理しています。詳しくは後ほど。

### helper

ここでは共通で使う関数をまとめています。例えば、`Validate.js`とか
routerとstartupについては今回の趣旨とあまり関係ないので割愛します。


## componentの構成について

### Atoms

SFC(Stateless Functional Component)で書くようにします。propsで受け取ったものを戻り値としてレンダリングする純粋な関数にします。ライフサイクルメソッドや`state`を扱うことはできませんが、stateを変更することができないということを明示的にできて良いなと思っています。

また、基本的にrenderされる要素は1つになるようにします。なので、いくつも要素が入るようならそれはAtomsではなくMoleculesになるはずです。

※ material-ui使ってる

``` jsx
import React from "react"
import TextField from 'material-ui/TextField/TextField'

const FormField = (props) => {
  return (
    <TextField
      {...props.input}
      // その他props
    />
  )
}

export default FormField
```

### Molecules

MoleculesについてもSFCで書くようにします。さらに、Moleculesは基本的には複数のAtomsもしくは場合によってはMoleculesで構成します。
ここでは複数のField(atoms)を組み合わせて入力フォームをまとめたものになっています。

んで、いつも悩むのがOrganismsとの違いです。自分はそこの判断をOrganismsはそれ単体で機能するものとしました。
今回の場合だと、Fieldのみだと入力するだけのものですが、そこにSubumitボタンなどを組み合わせた場合は「formを入力して送信」する機能として成り立ちます。

その「formを入力して送信」する機能をOrganismsになるように今回は設計をしました。


``` jsx
import React from "react"
import {Field} from 'react-final-form'
import FormField from "../../atoms/FormField"
import FieldDropZone from "../FieldDropZone"


const UpdateBookFields = (props) => (
  <CardText>
    <Field
      name="name"
      component={FormField}
    />
    // その他Field
  </CardText>
)

export default UpdateBookFields
```

### Organisms

OrganismsについてもSFCで書くようにします。AtomsとMoleculesを組み合わせて機能を作る感じです。
`UpdateFields`が入力部分のMoleculesで`UpdateActions`はsubmit部分のMoleculesですね。それらを組み合わせて先ほど書いた「formを入力して送信」する機能」として本の名前などの情報を入力してsubmitできるようにしています。

``` jsx
import React from "react"
import { Form } from 'react-final-form'
import Card from 'material-ui/Card/Card'
import CardTitle from 'material-ui/Card/CardTitle'
import UpdateFields from '../../molecules/book/UpdateFields'
import UpdateActions from '../../molecules/UpdateActions'
import ErrorHOC from '../../../containers/hoc/ErrorHOC'

const UpdateBookContent = (props) => {

  const initialValues = props.bookData.book ? {
    name: props.bookData.book.name,
    description: props.bookData.book.description,
    author: props.bookData.book.author,
    categoryId: props.bookData.book.category_id,
    url: props.bookData.book.url
  } : {}

  return (
    <Card>
      <CardTitle title={props.card.title} subtitle={props.card.subtitle}/>
      <Form
        initialValues={initialValues}
        onSubmit={props.onSubmit.method}
        render={({ handleSubmit }) => (
          <div>
            <UpdateFields {...props}/>
            <UpdateActions
              {...props}
              handleSubmit={handleSubmit}/>
          </div>
        )}
      />
    </Card>
  )
}

export default ErrorHOC(UpdateBookContent)
```

### Templates
ここでは、機能が完結しているOrganismsをrenderします。基本的には、ここではOrganisms以外のcomponentを使わないようにします。
またTemplatesは今までと違い`this.state`を使ってそのView全体の管理をしたいのでclassで作ります。
ここでは本のcreateができたかどうかをローカルstateで持っています。

またメソッドも定義してstateと共に子componentにバケツリレーされます。


``` jsx
import React from 'react'
import {FETCH_INITIAL_STATE, FETCH_SUCCEEDED_STATE, FETCH_IS_ERROR_STATE} from '../../actions/Fetch'
import {FETCH_ALL_BOOKS_QUERY} from '../../actions/Books'
import FoundationHOC from '../../containers/hoc/FoundationHOC'
import UpdateContent from '../organisms/book/UpdateContent'

class Books extends React.Component {
  constructor() {
    super()
    this.state = FETCH_INITIAL_STATE
  }

  onSubmit(values, e) {
    const {createBook} = this.props
    createBook({
      variables: {...values},
      refetchQueries: [{
        query: FETCH_ALL_BOOKS_QUERY
      }]
    }).then(() => {
      e.reset()
      this.setState(FETCH_SUCCEEDED_STATE)
    }).catch((errors) => {
      console.log(errors)
      this.setState(FETCH_IS_ERROR_STATE(errors))
    })
  }

  render() {
    return (
      <UpdateContent
        {...this.state}
        {...this.props}
        bookData={false}
        card={{title: '本の登録', subtitle: '本の登録をします'}}
        onHandleSelect={this.onHandleSelect.bind(this)}
        onHandleRemove={this.onHandleRemove.bind(this)}
        onSubmit={{label: '登録する', method: this.onSubmit.bind(this)}}
        onDelete={{}}
      />
    )
  }
}

export default FoundationHOC(Books)

```

### Pages
ここでは何もrenderをしません。templatesをGraphQLと連携させるだけです。
ここで初めてサーバーから受け取ったデータをpropsに渡されるイメージです。

nameで定義した`this.props`にデータだったり、メソッドが入ります。
`this.props.booksData`とか`this.props.createBook()`ですね。


もしRedux使うならここでコネクトさせる感じかなと思います。
ちなみに、他のプロジェクトで設計をした時はOrganismsにReduxとのコネクトを任せるように作ったことがあります。ようは、機能ごとに必要なstateをpropsに渡すようにしていました。この設計は最初はまあまあよかったのですが、ページ機能が増えてくるとページ内で同じStateを使ったりするので、propsに渡すのが何箇所もあったり、APIから取得するのも機能ごとなので実装者によっては何箇所にもあったり・・・
どうしても規模が大きくなるにつれやりにくさが出てきました。

問題点としては、APIから取得するとこの所在と使っているStateの場所がいくつもあり所在が把握しづらくなってしまいました。

なので、今回はOrganismsではStateを持たすことをやめてページ単位で存在するPagesに持たすって感じですね。

``` JavaScript
import Books from '../../components/templates/Books'
import { graphql, compose } from 'react-apollo'
import {FETCH_ALL_BOOKS_QUERY, CREATE_BOOK_MUTATION} from '../../actions/Books'

export default compose(
  graphql(FETCH_ALL_BOOKS_QUERY, {
    name: 'booksData'
  }),
  graphql(CREATE_BOOK_MUTATION, {
    name: 'createBook'
  })
)(Books)

```

## Reduxいらないと思った理由

`Apollo`によりGraphQLで取得したデータにしろ、Createだったりのメソッドも`props`に入れてくれます。
なので、`this.props.booksData`や`this.props.createBook()`というようにcomponentで扱うことができます。

だったら、別にReduxでState管理をしなくてもいいのではと思い今回のような実装にしました。


### ただReduxがないと困る部分もある
基本的にReduxを使わないとなると共通で何かしたい場合に困ります。

- 共通エラー周り
- 共通UI周り

とか

例えば、全ページで何かの一覧を表示させたいとか。今回の場合だと、book一覧を常にサイドバーに表示させています。
他にもユーザーのアカウント名を常にheaderに表示させたいなどはよくありますしね。

### HOCで解決できた
そういった問題もHigher Order Components(HOC)で解決できます。例えば先ほどのbook一覧を常にサイドバーに表示であれば、サイドバーにラップされたcomponentから受け取ったpropsのbookListを渡してやることでできます。
あと、ついでに共通処理としてページ遷移したらページトップへScrollさせる処理も入れときます。

``` jsx
import React from 'react'
import NavBar from '../../components/atoms/NavBar'
import Container from '../../components/atoms/Container'
import SideBar from '../../components/organisms/SideBar'

const FoundationHOC = (WrappedComponent) => {
  class Foundation extends React.Component {
    constructor() {
      super()
      this.state = {
        open: false
      }
    }


    handleToggle() {
      this.setState({
        open: !this.state.open
      })
    }

    componentDidUpdate(prevProps, prevState) {
      setTimeout(() => {
        /** ブラウザバックならreturn **/
        if (this.props.history.action === 'POP') {
          return
        }

        if(prevProps.match !== this.props.match) {
          /** それ以外はページトップへ移動 **/
          window.scrollTo(0, 0)
        }
      })
    }

    render() {
      const {booksData} = this.props
      return (
        <div>
          <NavBar
            {...this.state}
            onToggle={() => this.handleToggle()}/>
          <SideBar
            {...this.state}
            onToggle={() => this.handleToggle()}
            list={booksData.books}
          />
          <Container>
            <WrappedComponent {...this.props}/>
          </Container>
        </div>
      )
    }
  }
  return Foundation
}

export default FoundationHOC
```

あとは、使いたいcomponentをラップするだけです。

``` JavaScript
export default FoundationHOC(Books)
```
## 今でも悩んでいる部分
Atomic Designの概念としては、TemplatesでState(ローカルstate)を持たすのはちょっと違うのかもと思っています。ローカルstateもPagesで持った方が良いのかなと。

ただ、PageではGraphQLのを使うメソッドのみっていうのもわかりやすくて気に入っているから、どうしようかなと。この辺は実際にプロジェクトで使ってみると問題点が見つかっていいんですよね。

機会があれば試してみたいと思います。

## まとめ
とりあえず今回はReduxを使わずにGraphQL + Reactでの設計というかやってみた実装方法でした。
ポイントとしては、

- 基本的にSFC(Stateless Functional Component)で書くようにしたことで、Stateの所在がわかりやくなった
- HOCを使うことで共通化も簡単

というか、GraphQL + ReactがいいというよりかはHOCの効能というか破壊力を体感できたの大きかったように感じます。