redux-formでSubmissionErrorを使わずにエラーを表示する

reactを使って開発することが多いのですが、
adminサイトなどではよくredux-formを使ったりしています。

サーバからのエラー等は、[公式ドキュメント](https://redux-form.com/7.4.2/docs/api/submissionerror.md/)の通りに
redux-formのhandleSubmitの中でSubmissionErrorを投げてあげれば大体は上手くいくんですが……

残念ながら、既存の改修でハチャメチャが押し寄せてくる状態であったり、
dispatchの中の処理でAPI投げているのでSubmmisonErrorが取れない！とか、
上手くいかない場合が多々あったので、自由にエラーを設定できないか試してみました。

こうするともっと上手くいくよ！というのがあればコメント等もらえると。

# 作ってみたもの
![custom_error.gif](https://qiita-image-store.s3.amazonaws.com/0/235100/67c00ed1-5527-f9fd-0988-cc14ad8b6135.gif)

Log Inを押した時には、実際のサーバエラー想定の結果を、
Set Errorを押した時には全く関係ない場所から、redux-formの各項目に対してエラーを設定しています。

## demo
https://redux-form-custom-error.storage.googleapis.com/index.html

## repository
https://github.com/fumihiko-hidaka/redux-form-custom-error

## redux-form document
https://redux-form.com/7.4.2/examples/submitvalidation/
https://redux-form.com/7.4.2/docs/api/actioncreators.md/

# 実装内容

https://github.com/fumihiko-hidaka/redux-form-custom-error/blob/master/src/AppForm.jsx

```jsx
import React from 'react';
import { connect } from 'react-redux';
import {
  Field,
  reduxForm,
  SubmissionError,
  stopSubmit as stopSubmitAction,
} from 'redux-form';
import compose from 'recompose/compose';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const renderField = ({ input, label, type, meta: { touched, error } }) => (
  <div>
    <label>{label}</label>
    <div>
      <input {...input} placeholder={label} type={type} autoComplete="off" />
      {touched && error && <span>{error}</span>}
    </div>
  </div>
);

const AppForm = (props) => {
  const {
    error,
    handleSubmit,
    pristine,
    reset,
    submitting,
    touch,
    stopSubmit,
  } = props;

  return (
    <form onSubmit={handleSubmit(() => {
      return sleep(1000).then(() => {
        throw new SubmissionError({
          password: 'Wrong password',
          _error: 'Login failed!'
        })
      });
    })}>
      <Field
        name="username"
        type="text"
        component={renderField}
        label="Username"
      />
      <Field
        name="password"
        type="password"
        component={renderField}
        label="Password"
      />
      {error && <strong>{error}</strong>}
      <div>
        <button type="submit" disabled={submitting}>
          Log In
        </button>
        <button
          type="button"
          disabled={pristine || submitting}
          onClick={reset}
        >
          Clear Values
        </button>
        <button
          type="button"
          disabled={submitting}
          onClick={() => {
            touch(...['username', 'password']);
            stopSubmit('app-form', {
              username: '話は聞かせてもらった',
              password: '人類は滅亡する！',
              _error: 'な……なんだってー！！'
            })
          }}
        >
          Set Error
        </button>
      </div>
    </form>
  );
};

const enhance = compose(
  reduxForm({ form: 'app-form' }),
  connect(null, {
    stopSubmit: stopSubmitAction
  }),
);

export default enhance(AppForm);
```

内容は簡単、stopSubmitとtouchを使っているだけです。

redux-formのソースを読んでみると一発なんですが、
SubmissionErrorをthrowした先で、stopSubmit関数を呼んであげています。
この関数に対してエラーデータを渡しているので、自由に呼びたい場合には、
それと同様に呼び出したい場所でstopSubmitを実行すればOKです。
※touchしておかないと、カーソルを合わせる前にボタンを押した際に正常に動かないのでそこだけ注意が必要です。

今回はボタンを押下したタイミングで呼び出していますが、reducerの中の処理でもどこでも実装は可能です。
reducerの処理の中でやるのが適切かどうかは置いておきますが……

## 最後に
[株式会社ネコカリ](https://www.nekokari.co.jp/)では猫の手も借りたい🔥炎上中🔥なお仕事を募集しています！
一緒に働くメンバーも募集していますので、よかったら是非！