React.js

ReactのAPI POST処理:postJsonでフォームを送信する

この記事でわかること

入力検証、JSON送信、二重送信の抑制、HTTPエラー、204応答とサーバー側の責任を分けて解説します。

入力検証、JSON送信、二重送信の抑制、HTTPエラー、204応答とサーバー側の責任を分けて解説します。

postJsonの役割

フォーム操作をAPIへ渡す独自関数

postJson はJSONをPOSTする自作関数です。React自体に保存先を用意する機能はありません。以下は同じオリジンの /api/messages がPOSTを受け付ける前提で、Viteの静的ファイルだけでは保存できません。後半のAPI契約に沿うサーバーを別途用意します。

GETは取得、POSTはサーバーへ処理を依頼する用途です。送信はフォームの onSubmit で行い、画面が表示されるだけで登録されるEffectにはしません。

src/api/postJson.tsを作る

JSON応答と空の204応答を扱う

TypeScript
export async function postJson(url: string, body: unknown): Promise<unknown> {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  if (response.status === 204) return null;
  return response.json();
}

この関数はJSONへ変換できる値を渡す契約です。循環参照や BigInt を含む値は変換で失敗します。成功時はJSONまたは204を返すAPIに使い、200なのに空本文を返すAPIには契約に合わせた変更が必要です。MDNのFetch解説も参照してください。

src/App.tsxでフォームを作る

入力と送信状態を分ける

TSX
import { useRef, useState, type FormEvent } from 'react';
import { postJson } from './api/postJson';

export default function App() {
  const [text, setText] = useState('');
  const [sending, setSending] = useState(false);
  const [notice, setNotice] = useState('');
  const busy = useRef(false);
  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (busy.current) return;
    const message = text.trim();
    if (!message || message.length > 200) {
      setNotice('1〜200文字で入力してください');
      return;
    }
    busy.current = true;
    setSending(true);
    setNotice('');
    try {
      await postJson('/api/messages', { message });
      setNotice('送信しました');
      setText('');
    } catch {
      setNotice('送信結果を確認できません。履歴を確認してから再操作してください');
    } finally {
      busy.current = false;
      setSending(false);
    }
  }
  return <main>
    <h1>メッセージ送信</h1>
    <form onSubmit={handleSubmit}>
      <label>メッセージ
        <textarea value={text} onChange={event => setText(event.target.value)}
          required maxLength={200} disabled={sending} />
      </label>
      <button type="submit" disabled={sending}>
        {sending ? '送信中' : '送信'}
      </button>
    </form>
    <p role="status">{notice}</p>
  </main>;
}

preventDefault() でブラウザーの通常のフォーム遷移を止めます。stateは表示に使い、refは再レンダーを待たずに連続呼び出しを抑えます。送信中は入力も無効にするため、送信後にユーザーが新しく書いた文字を消す問題を避けています。ここでの文字数はJavaScript文字列のUTF-16コード単位数であり、絵文字などの見た目の文字数とは一致しない場合があります。

API側に必要な処理

このサンプルが期待する契約

  • POST /api/messagesapplication/json を受け取る。
  • message が文字列か、長さや内容をサーバー側でも検証する。
  • 認証・認可を確認し、保存に成功した場合は201とJSON、または204を返す。
  • 入力不正は400など適切なHTTPステータスにする。
  • Cookie認証ならCSRF対策を含めて設計し、機密情報をレスポンスへ含めない。

開発用のモックで確認する場合は「保存していない」ことを区別してください。ネットワークパネルでURL・メソッド・JSON本文・応答を確認し、実APIでは保存先の記録も確認します。UIの成功表示だけを永続保存の証拠にはしません。

二重登録と失敗時の扱い

ボタン無効化だけでは重複を防げない

複数タブ、通信の再送、タイムアウト後の再操作はクライアントのロックだけでは防げません。注文や決済などでは、サーバー側で冪等性キーや一意制約を設計します。POSTが失敗したように見えても、サーバーでは保存済みの場合があります。無条件で自動再送せず、処理結果を照会できる設計にします。

確認するケース

空白だけの入力、上限超過、二重クリック、400・500、オフライン、成功JSON、204を試します。失敗時には入力が残り、成功時だけ空になることを確認します。ブラウザーへ管理用APIキーを埋め込まず、サーバーが各リクエストの権限を判断してください。

取得の処理はfetchJson、状態の再利用はuseToggleで学べます。

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →