Next.jsで学ぶReact講座(完全初心者向け・30日)演習問題・課題 | 第4章:実用的React – UX対応

React Next.js
スポンサーリンク

この課題のねらい

ここは「ただ動くアプリ」から「ちゃんと気配りされたアプリ」に一段上げるところです。
テーマは UX(ユーザー体験)対応──つまり、

ローディング中に何も出さない“無言の時間”をなくす
エラーが起きたときに、ユーザーを置き去りにしない
失敗しても「もう一回試せる」逃げ道を用意する

この 3 つを、React の state と条件分岐でちゃんと表現していきます。


ベースとなる「データ取得コンポーネント」

まずは、前の「データ取得」の回で作ったようなベースを用意します。

"use client";

import { useEffect, useState } from "react";

type Post = {
  id: number;
  title: string;
};

export default function PostsPage() {
  const [posts, setPosts] = useState<Post[] | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  async function fetchPosts() {
    try {
      setIsLoading(true);
      setError(null);

      const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");

      if (!res.ok) {
        throw new Error(`HTTPエラー: ${res.status}`);
      }

      const data: Post[] = await res.json();
      setPosts(data);
    } catch (err) {
      console.error(err);
      setError("データの取得に失敗しました。時間をおいて再度お試しください。");
    } finally {
      setIsLoading(false);
    }
  }

  useEffect(() => {
    fetchPosts();
  }, []);

  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      {/* ここに UX 対応を足していく */}
    </main>
  );
}
TSX

ここに「ローディング表示」「エラー表示」「再取得ボタン」を肉付けしていきます。


必須課題① ローディング表示

「何も出ていない時間」をなくす

ユーザーからすると、「クリックしたのに何も起きてないように見える時間」が一番不安です。
そこで、isLoadingtrue の間は、はっきり「読み込み中」と伝えます。

if (isLoading) {
  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <p>読み込み中です...</p>
    </main>
  );
}
TSX

このようにコンポーネントの先頭で早期 return してしまうと、

ローディング中は「読み込み中です…」だけ
読み込みが終わったら、下の通常表示に切り替わる

という分かりやすい挙動になります。

もう少し UX を上げるなら、簡単なスピナー風の見た目にしてもいいです。

if (isLoading) {
  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
        <span
          style={{
            width: "14px",
            height: "14px",
            borderRadius: "9999px",
            border: "2px solid #e5e7eb",
            borderTopColor: "#3b82f6",
            animation: "spin 0.8s linear infinite",
          }}
        />
        <span>読み込み中です...</span>
      </div>
    </main>
  );
}
TSX

本番なら CSS でアニメーションを書くところですが、今は「ローディング中だと分かる表示」があれば十分です。


必須課題② エラー表示

「失敗したこと」と「どうしてほしいか」を伝える

エラーが起きたときに、ただ何も表示しないのは一番つらいです。
error state にメッセージが入っているときは、それを画面に出します。

if (error) {
  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <p style={{ color: "red", marginBottom: "8px" }}>{error}</p>
      <p style={{ fontSize: "14px", color: "#6b7280" }}>
        ネットワーク環境を確認して、しばらくしてから再度お試しください。
      </p>
    </main>
  );
}
TSX

ここで意識してほしいのは、

「失敗しました」で終わらせない
「どうしてほしいか」を一言添える

ということです。

ユーザー目線で見ると、

何が起きたのか分からない
自分が何をすればいいのか分からない

この 2 つが一番ストレスなので、そこを潰してあげるイメージです。


通常時の表示(成功パターン)

ローディングでもエラーでもないときに、データを表示します。

if (isLoading) {
  // ローディング表示(さっきのやつ)
}

if (error) {
  // エラー表示(さっきのやつ)
}

return (
  <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
    <h1>記事一覧</h1>
    {posts && (
      <ul style={{ marginTop: "12px" }}>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    )}
  </main>
);
TSX

この 3 段階の分岐が、「UX 対応されたデータ取得コンポーネント」の基本形です。


挑戦課題 再取得ボタン追加

「失敗したら終わり」ではなく「やり直せる」ようにする

ここからが UX の一歩踏み込んだところです。
エラーが出たときに、「再取得」ボタンを用意して、ユーザーが自分でリトライできるようにします。

さっきの fetchPosts 関数は、すでにコンポーネントの外側に切り出してあるので、それをそのままボタンから呼び出せます。

エラー表示部分をこう変えます。

if (error) {
  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <p style={{ color: "red", marginBottom: "8px" }}>{error}</p>
      <p style={{ fontSize: "14px", color: "#6b7280", marginBottom: "12px" }}>
        ネットワーク環境を確認して、再度取得をお試しください。
      </p>
      <button
        onClick={fetchPosts}
        style={{
          padding: "8px 16px",
          borderRadius: "9999px",
          border: "none",
          backgroundColor: "#3b82f6",
          color: "white",
          cursor: "pointer",
          fontSize: "14px",
        }}
      >
        再取得する
      </button>
    </main>
  );
}
TSX

これで、

取得に失敗する
エラー文と「再取得する」ボタンが出る
ボタンを押すと fetchPosts が再度実行される

という流れになります。

ローディング中の再取得ボタンの扱い

もう一歩だけ丁寧にするなら、「再取得中はボタンを無効化する」こともできます。

<button
  onClick={fetchPosts}
  disabled={isLoading}
  style={{
    padding: "8px 16px",
    borderRadius: "9999px",
    border: "none",
    backgroundColor: isLoading ? "#9ca3af" : "#3b82f6",
    color: "white",
    cursor: isLoading ? "not-allowed" : "pointer",
    fontSize: "14px",
  }}
>
  {isLoading ? "再取得中..." : "再取得する"}
</button>
TSX

こうしておくと、

連打されて無駄に何回も fetch が走る
「押したのかどうか分からない」

といったストレスを減らせます。


まとめ:UX対応でつかんでほしいこと

この課題で本当に持ち帰ってほしいのは、次の感覚です。

ローディング中は「何もない時間」を作らず、はっきり「読み込み中」と伝える
エラー時は「失敗したこと」と「どうしてほしいか」をセットで出す
再取得ボタンを用意して、「失敗したら終わり」ではなく「やり直せる」体験にする

これができると、同じ「データ取得コンポーネント」でも、

ただ動くだけの画面
ちゃんとユーザーに寄り添った画面

の差が、はっきり出てきます。

タイトルとURLをコピーしました