Skip to content
This repository has been archived by the owner on Sep 8, 2022. It is now read-only.

Step4:フォームで入力した値をAPIに送信する

Tomohide Takao edited this page Apr 25, 2019 · 4 revisions

redux-pluto では入力フォームの制御やバリデーションに redux-form を使用してます。
redux-form を使用して入力フォームを作成し、入力した値をAPIに送信してみましょう。

モックAPIの準備

まずは form で入力した情報の送信先であるところの API 定義から作成していきましょう。

agreed に post リクエスト用のAPIを用意

path が '/hello' method が post のAPIを追加しましょう。

spec/agreed/hello/postComment.ts

+import { APIDef, POST, Success201, ResponseDef } from "agreed-typed";
+
+export type PostCommentAPI = APIDef<
+  POST,
+  ["hello"],
+  {}, // header
+  {}, // query
+  { text: string }, // request body
+  {}, // response header
+  ResponseDef<Success201, { text: string }>
+>;
+
+const api: PostCommentAPI = {
+  request: {
+    path: ["hello"],
+    method: "POST",
+    body: {
+      text: "{:text}",
+    },
+    values: {
+      text: "hello",
+    },
+  },
+  response: {
+    status: 201,
+    body: {
+      id: "0003",
+      text: "hello",
+    }
+  },
+};
+
+module.exports = api;

次に定義したファイルを agreed.ts ファイルに追加し、export させます。

spec/agreed/agreed.ts

   ...flatten([
     require("./agreedsample/get"),
     require("./uploadsample/post"),
     require("./hello/getComments"),
+    require("./hello/postComment"),
   ]),
 );

BFFの service に送信用の処理を追加

~~~
 import Axios from "axios";
-import { read } from "./utils"; // axios を使い get request を投げる util
+import { read, create } from "./utils"; // axios を使い get request を投げる util

 export default class Hello {
   name: string;
~~~
   read(req: any, resource: any, params: any = {}, config: any) {
     return read(this.axios, this.name, this.pathname, params, {});
   }
+
+  create(req: any, resource: any, params: any, body?: any, config?: any) {
+    return create(this.axios, this.name, this.pathname, body, params, {});
+  }
 }

入力値をAPIに送信する処理の作成

BFF の API に値を送信する処理の追加

step4 と同様に リクエストを飛ばす処理を作成するので、
リクエスト送信・成功・失敗それぞれの Action type, ActionCreater, Reducer を作成します。

src/shared/redux/modules/hello.ts

~~~
 import { steps } from "redux-effects-steps";
-import { fetchrRead } from "redux-effects-fetchr";
+import { fetchrRead, fetchrCreate } from "redux-effects-fetchr";

 /**
  * Action types
~~~
 const HELLO_GET_COMMENTS_REQUEST = "redux-pluto/hello/get/comments/request";
 const HELLO_GET_COMMENTS_SUCCESS = "redux-pluto/hello/get/comments/success";
 const HELLO_GET_COMMENTS_FAIL = "redux-pluto/hello/get/comments/fail";
+const HELLO_POST_COMMENT_REQUEST = "redux-pluto/hello/post/comments/request";
+const HELLO_POST_COMMENT_SUCCESS = "redux-pluto/hello/post/comments/success";
+const HELLO_POST_COMMENT_FAIL = "redux-pluto/hello/post/comments/fail";

 type ChangeVisibility = {
   type: typeof HELLO_CHANGE_VISIBILITY;
~~~
   type: typeof HELLO_GET_COMMENTS_FAIL;
   error: boolean;
 };
-
+type PostCommentRequest = {
+  type: typeof HELLO_POST_COMMENT_REQUEST;
+  payload: {
+    resource: string;
+    body: {
+      text: string;
+    };
+  };
+};
+type PostCommentSuccess = {
+  type: typeof HELLO_POST_COMMENT_SUCCESS;
+  payload: {
+    data: {
+      id: string;
+      text: string;
+    };
+  };
+};
+type PostCommentFail = {
+  type: typeof HELLO_POST_COMMENT_FAIL;
+  error: boolean;
+};
 type Action =
   | ChangeVisibility
   | CommentsRequest
   | CommentsSuccess
-  | CommentsFail;
+  | CommentsFail
+  | PostCommentRequest
+  | PostCommentSuccess
+  | PostCommentFail;

 /**
  * Action creators
~~~
   );
 }

+export function postCommentRequest(payload: {
+  resource: string;
+  body: { text: string };
+}): PostCommentRequest {
+  return {
+    type: HELLO_POST_COMMENT_REQUEST,
+    payload,
+  };
+}
+
+export function postCommentSuccess(payload: {
+  data: { id: string; text: string };
+}): PostCommentSuccess {
+  return {
+    type: HELLO_POST_COMMENT_SUCCESS,
+    payload,
+  };
+}
+
+export function postCommentFail(): PostCommentFail {
+  return {
+    type: HELLO_POST_COMMENT_FAIL,
+    error: true,
+  };
+}
+
+export function postComment(body: { text: string }) {
+  return steps(
+    postCommentRequest({ resource: "hello", body }),
+    ({ payload }) => fetchrCreate(payload),
+    [postCommentSuccess, postCommentFail],
+  );
+}
+
 /**
  * Initial state
  */
~~~
         loaded: false,
       };
     }
+    case HELLO_POST_COMMENT_REQUEST: {
+      return {
+        ...state,
+        loading: true,
+        loaded: false,
+      };
+    }
+    case HELLO_POST_COMMENT_SUCCESS: {
+      const {
+        data: { id, text },
+      } = action.payload;
+      return {
+        ...state,
+        comments: [...state.comments, { id, text }],
+        loading: false,
+        loaded: true,
+      };
+    }
+    case HELLO_POST_COMMENT_FAIL: {
+      const { error } = action;
+      return {
+        ...state,
+        error,
+        loading: false,
+        loaded: false,
+      };
+    }
     default: {
       return state;
     }

フォームの作成

reducer の準備が整ったので、次は画面上で値を入力するフォームを作成していきます

component に reduxForm を追加する

organisms/Hello/index.ts

~~~
 import { compose } from "redux";
 import { connect } from "react-redux";
 import { asyncLoader } from "redux-async-loader"; // redux-asynch-roder から asyncLoader をインポート
+import { reduxForm } from "redux-form";
 import { changeVisibility, getComments } from "../../../redux/modules/hello";
 import { RootState } from "../../../redux/modules/reducer";
 import Hello from "./Hello";
~~~
       onChangeVisibility: () => dispatch(changeVisibility()), // changeVisibilityを store に dispatchする関数を返す
     }),
   ),
+  reduxForm({
+    form: "hello",
+  }),
 )(Hello);

organisms/Hello/Hello.tsx

~~~
 import React from "react";
+import { Field } from "redux-form";

 export type Props = {
   // props の型定義
~~~
       <button type="button" onClick={() => onChangeVisibility()}>
         {isVisible ? "hide" : "show"}
       </button>
+      <div>
+        <Field name="text" component="input" type="text" />
+      </div>
     </div>
   );
 }

動作確認

store を見ると、form.hello としてフォームが追加されており、
テキストフィールドに入力することでその値が store に反映されている事がわかります。

submit処理を追加する

form を追加する

Redux Form から値を取得するために、form タグ を追加し、入力値を handleSubmit で処理するように変更します。

Container component
reduxForm 内に handleSubmit が発火した際に form の値を Store に dispatch するonSubmit(values, dispatch)関数を定義します。
第1引数の values には form 内に定義した field の values オブジェクトが入ります。

organisms/Hello/index.ts

~~~
 import { connect } from "react-redux";
 import { asyncLoader } from "redux-async-loader"; // redux-asynch-roder から asyncLoader をインポート
 import { reduxForm } from "redux-form";
-import { changeVisibility, getComments } from "../../../redux/modules/hello";
+import {
+  changeVisibility,
+  getComments,
+  postComment,
+} from "../../../redux/modules/hello";
 import { RootState } from "../../../redux/modules/reducer";
 import Hello from "./Hello";

~~~
   ),
   reduxForm({
     form: "hello",
+    onSubmit(values: { text: string }, dispatch: any) {
+      dispatch(postComment(values));
+    },
   }),
 )(Hello);

Presentational component
form と submit 用の button を追加します。

organisms/Hello/Hello.js

~~~
 };

 export default function Hello(props: Props) {
-  const { isVisible, onChangeVisibility, comments } = props;
+  const { isVisible, onChangeVisibility, comments, handleSubmit } = props;
   return (
     <div>
       {isVisible &&
~~~
       <button type="button" onClick={() => onChangeVisibility()}>
         {isVisible ? "hide" : "show"}
       </button>
-      <div>
-        <Field name="text" component="input" type="text" />
-      </div>
+      <form onSubmit={handleSubmit}>
+        <div>
+          <Field name="text" component="input" type="text" />
+          <button type="submit">submit</button>
+        </div>
+      </form>
     </div>
   );
 }

動作確認

テキストを入力して submit ボタンをクリックすると API に値が送信され、そのレスポンスにより画面が書き換わります。

このSTEPのソースコード