redux-formを使って投稿画面を実装する【初学者のReact×Railsアプリ開発 第7回】

#やったこと
* redux-formを使ってReactで投稿画面を実装した
* axiosを使って、Railsで実装したAPIと連携させ、投稿できるようにした。

#成果物
![ohy01-019mn.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367226/550cbb13-4524-f71d-d8c7-6ca7e1fbbd3e.gif)


#実装手順
###モジュールのインストール
アプリ開発全体で使うモジュールはここでインストールしています。

###App.js
```javascript:App.js
import Login from './containers/Create';

            <Auth>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path='/create' component={Create} />
              </Switch>
            </Auth>
```
###reducers/rootReducer.js
* redux-formからformReducerを読み込んでいる。


```javascript:rootReducer.js
import { combineReducers } from 'redux'
import { reducer as formReducer } from 'redux-form'
import { routerReducer } from 'react-router-redux'
import CurrentUserReducer from './CurrentUserReducer'

const rootReducer = combineReducers({
  CurrentUserReducer,
  form: formReducer,
  router: routerReducer,
})

export default rootReducer
```

###containers/Create.js
* これが投稿画面。
* フォームCreateForm.jsをインポートしている
* submitPostで送信の処理を書いている。
* フォーム内の改行などはreplaceなどを使って消去している
* axiosの使い方はここで書いている通りで、localStorageからauth_tokenなどを呼び出して、headerにつけて送っていることで認証できる。
* 送信後のフォームの処理は、本当はactionを経由しなければいけないのかな〜...（この実装方法だと、タイミングは微妙に変）
* 

```javascript:Create.js
import React from 'react'
import CreateForm from '../components/CreateForm'
import axios from 'axios'

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import * as actions from '../actions';

class Create extends React.Component {
  constructor(props) {
    super()
    this.state = {
      postSuccess: false,
    }
  }
  submit = values => {
    const data = {
      content: values.notes,
      user_id: 1
    }
    axios.post(process.env.REACT_APP_API_URL + '/api/v1/posts', data)
  }

  submitPost = values => {
    const { CurrentUserReducer } = this.props;
    var value_content = values.notes
    var send_content = value_content.replace(/\r?\n/g, "");

    const data = {
      content: send_content,
      user_id: CurrentUserReducer.items.id
    }
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid

    axios.post(process.env.REACT_APP_API_URL + '/api/v1/posts', data, {
      headers: {
        'access-token': auth_token,
        'client': client_id,
        'uid': uid
      }
    })
      .then(() => {
      })
      .catch(() => {
        values.notes = value_content
      })
    values.notes = ""

  }

  render() {
    const { actions } = this.props;
    const { CurrentUserReducer } = this.props;

    const { classes } = this.props;
    return (
      <div>
        <h3>テーマを投稿する</h3>
        <CreateForm onSubmit={this.submitPost} />
      </div>
    )
  }
}

const mapState = (state, ownProps) => ({
  CurrentUserReducer: state.CurrentUserReducer
});

function mapDispatch(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
  };
}

export default connect(mapState, mapDispatch)(Create);
```

###components/CreateForm.js
* validateで必須項目や50字以内にするように指定している。
* const CreateForm内のFieldタグで色々オプションを付けている。multilineは複数行のフォームにするかどうか？で、rowsMaxで最大の行数を指定できる。Enter押すと自動で行数が増えていく。

```javascript:CreateForm.js
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import TextField from '@material-ui/core/TextField'
import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(theme => ({
  button: {
    margin: theme.spacing(1),
  },
  field: {
    width: '80%',
    height: '200px',
  }
}));

const validate = values => {
  const errors = {}
  const requiredFields = [
    'notes'
  ]
  requiredFields.forEach(field => {
    if (!values[field]) {
      errors[field] = 'Required'
    }
  })
  if (
    values.notes &&
    values.notes.length > 51
  ) {
    errors.notes = '50字以内にしてください'
  }
  return errors
}

const renderTextField = ({
  label,
  input,
  meta: { touched, invalid, error },
  rows = 3,
  ...custom
}) => (
    <TextField
      label={label}
      placeholder={label}
      error={touched && invalid}
      helperText={touched && error}
      {...input}
      rows={rows}
      {...custom}
      variant="outlined"
    />
  )

const CreateForm = props => {
  const { handleSubmit, pristine, reset, submitting } = props
  const classes = useStyles();
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <Field
          name="notes"
          component={renderTextField}
          className={classes.field}
          label=""
          multiline
          rowsMax="7"
          margin="normal"
        />
      </div>
      <div>
        <Button
          variant="contained"
          color="primary"
          type="submit"
          className={classes.button}
        //endIcon={<Icon>send</Icon>}
        >
          Send
      </Button>
      </div>
    </form>
  )
}

export default reduxForm({
  form: 'CreateForm', // a unique identifier for this form
  validate,
})(CreateForm)
```