JavaScript初心者がReact+Reduxでブログのフロントエンドを作成するまで


# はじめに
[JavaScriptの基礎知識](https://qiita.com/tomoyamachi/items/b398f35882fb57b975ad)がだいたいわかったので、スキマ時間(4時間くらい)で簡単なブログのフロントエンドをReact+Reduxで構築した。
といっても、[Modern React with Redux](https://www.udemy.com/react-redux/learn/v4/content)というコースのセクション9を見ながら手を動かしただけ。JavaScript初心者だが、セクション9から見始めても特に問題なかった。

# ソースコード
実装時のメモも含めて、以下に保存してある。
[github.com/tomoyamachi/react-blog-sample](https://github.com/tomoyamachi/react-blog-sample)

# できるようになったこと
- SPAだが、URLごとで表示するDOMを分ける
- 非同期でAPI通信を行う
- 非同期処理が完了してから、表示処理を実行する
- 送信フォームの管理(バリデーション含む)
- ESの言語仕様がざっくりわかる
- 一人である程度のプロジェクトをつくる

# 今後
実践でそのまま利用できそうなことが詰まっていた。作成後1時間くらいはできたコードをいじって、各コードの役割を理解した。
次は、テストやコード品質の担保をしたいので、[Advanced React and Redux](https://www.udemy.com/react-redux-tutorial/)を見ようと思っている。

もし、React+Reduxを利用した中規模以上プロジェクトのソースコードが公開されてれば、かなりレベル上げができそう。
軽く探しているが見当たらないので、もしあれば知りたい。


# 以下、パッケージなどのメモ
## SPAだが、URLごとで表示内容を出し分けたい

[react-router-dom]() を利用。`Switch`を使わない場合、パスの正規表現に引っかかったものすべてのコンポーネントが表示される。

なお、parameterを取得したいときは、Componentの `this.props.match.params.{name}`の形で取得できる。

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import reducers from './reducers';
import promise from 'redux-promise';
import PostsIndex from './components/posts_index';
import PostsNew from './components/posts_new';
import PostsShow from './components/posts_show';
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <BrowserRouter>
      <div>
        <Switch>
          <Route path="/posts/new" component={PostsNew} />
          <Route path="/posts/:id" component={PostsShow} />
          <Route path="/" component={PostsIndex} />
        </Switch>
      </div>
    </BrowserRouter>
  </Provider>
  , document.querySelector('.container'));

```

## API通信がしたい

[axios](https://github.com/axios/axios)を利用。
レスポンスが返ってきてからの処理を入れる場合は、`axios.{HTTPmethod}().then()` でつなげる。エラー時の挙動は`.catch()`で管理

```javascript:actions/index.js
export function createPost(values, callback) {
    const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, values)
          .then(() => callback())
          .catch((e) => console.log(e));
    return {
        type: CREATE_POSTS,
        payload: request
    };
}
```

## 非同期処理を管理したい
[redux-promise](https://github.com/acdlite/redux-promise)を利用する。ミドルウェアに入れるだけで、利用できるようになる。非同期処理が完了してから、処理を行えるようになる。 ※細かなことは分かってない

```jsx:
const createStoreWithMiddleware = applyMiddleware(promise)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
...(略)
```

## フォームの管理がしたい
[redux-form](https://redux-form.com/7.1.2/) を利用。
バリデーションはreduxFormの引数に`validate` を指定すればいい。
フォーム操作後にエラーを表示したい場合は、`meta.touched`で判定。

```jsx:
import React, {Component} from 'react';
import {Field, reduxForm} from 'redux-form';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';

class PostsNew extends Component {
    renderField(field) {
        const {meta: { touched, error }} = field;
        const className = `form-group ${touched && error ? 'has-danger' : ''}`;

        return (
            <div className={className}>
              <label>{field.label}</label>
              <input
                className="form-control"
                type="text"
                {...field.input}
                />
              <div className="text-help">
                {field.meta.touched ? field.meta.error : ''}
              </div>
            </div>
        );
    }

    onSubmit(values) {
        this.props.createPost(values, () => {
            this.props.history.push('/');
        });
    }

    render() {
        const { handleSubmit } = this.props;
        return (
            <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
              <Field
                label="Title"
                name="title"
                component={this.renderField}
                />
              <button type="submit" className="btn btn-primary">Submit</button>
            </form>
        );
    }
}

function validate(values) {
    const errors = {};
    if(!values.title) {
        errors.title = "enter a title!";
    }
    return errors;
}

export default reduxForm({
    validate,
    form: 'PostsNewForm'
})(
    connect(null,{ createPost })(PostsNew)
);
```