0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

API を使ってタイトル候補をリアルタイム提案する UI を実装してみた

0
Last updated at Posted at 2026-02-15

はじめに

今回は、ユーザーがタイトルを入力すると同時に、API を使って「タイトル候補」をリアルタイムで提示する UI を実装してみました。
狙っている UX は次のような流れになります。

1. ユーザーがタイピングを開始
2. バックエンド API を呼び出し:
isTitleLoadingtrue になり「候補取得中...」と表示
3. オートコンプリートの提示:
titleSuggestions にデータが入ると、datalist を通じて候補が表示される

1. input と datalist の紐付けは必須

datalist を機能させるには、input 側の list 属性と、datalist 側の id を完全に一致させる必要があります。

<input 
  id="title"
  value={formData.title}
  list="title-suggestions"  // IDと一致させる
  onChange={handleTitleChange}
  placeholder="作品タイトルを入力..."
  required
/>

<datalist id="title-suggestions">
  {titleSuggestions.map((title) => (
    <option key={title} value={title} />
  ))}
</datalist>

注意:
これを忘れると、バックエンドからデータが届いても UI 上に候補リストが表示されません。

2. APIからタイトル呼び出し

1. ユーザーがタイトルを入力する
2. useEffect が発火する
3. 入力が 400ms 止まると検索開始(デバウンス)
4. setTitleSuggestions(uniqueTitles) で候補を state に保存
5. datalist がその state を使って option を生成

useEffect(() => {
  const title = formData.title.trim();
  if (!title) {
    setTitleSuggestions([]);
    return;
  }

  const timeout = setTimeout(async () => {
    const titlesData = await searchtitlesData(title);
    setTitleSuggestions(titlesData);
  }, 400);

  return () => clearTimeout(timeout);
}, [formData.title]);

API の呼びすぎを防ぐための対策

有料の API を利用している場合、最適化を行わないと 1 文字打つたびに料金が発生し、すぐにレート制限(429 Error)にかかってしまいます。また入力のたびに API を叩くと、ネットワーク負荷が増大し、動作が重くなる原因になります。そのため、以下のような対策を行うことが定石となります。

Debounce(デバウンス)の実装:
ユーザーがタイピングを止めてから一定時間(例: 0.5秒)経過した後にのみ API を実行します。

// lodashのdebounceなどを使用する例
const debouncedFetch = useCallback(
  debounce((value) => {
    fetchTitleSuggestions(value);
  }, 500),
  []
);

const handleTitleChange = (e) => {
  const value = e.target.value;
  setTitle(value);
  // タイピングのたびに実行されるが、実際のAPI呼び出しは500ms後
  debouncedFetch(value);
};

文字数制限の追加:
「1文字入力されただけで全件検索」のような無駄を防ぐため、3文字以上の場合のみ実行するガード節を入れます。

if (value.trim().length >= 3) {
  debouncedFetch(value);
}

3. Next.js でのローディング演出

isTitleLoading フラグを利用して「取得中...」の状態を可視化します。これにより、ユーザーは「今、システムが考えているんだな」と認識でき、UX が向上します。

{isTitleLoading && (
  <p className="mt-2 text-xs text-blue-500">
    タイトル候補を取得中...
  </p>
)}

まとめ

いかがだったでしょうか?
私が現在制作しているアプリで使用している API は課金制ではないため、現状では制限を強く意識する必要はありませんが、今回紹介した Debounce文字数制限 は、コスト削減だけでなく、サーバー負荷の軽減にも直結する必須技術です。
これらを活用して、スムーズでスマートな UX を追求していきましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?