カスタムのSuspenseを作ると便利

# はじめに
最近のReact, Next.jsを用いたアプリケーション開発では、Suspenseを用いることが必須となっています。
今回は、Suspenseコンポーネントをカスタムで作成して、場所によってスタイルを変えられるようにしておくと便利だよという記事を書いていきます。

# サンプルコード
以下は、
``` cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export const cn = (...inputs: ClassValue[]): string => twMerge(clsx(inputs));

```

``` custom-suspense.tsx
import { Suspense } from "react";
import { cn } from "./cn";

export type CustomSuspenseProps = Omit<React.SuspenseProps, "fallback"> &
  Pick<React.CSSProperties, "height" | "width"> & { className?: string };

export function CustomSuspense({
  width,
  height,
  className,
  ...rest
}: CustomSuspenseProps) {
  return (
    <Suspense
      fallback={
        <div
          style={{ width, height }}
          className={cn("inline-block animate-pulse bg-gray-200", className)}
        />
      }
      {...rest}
    />
  );
}

```

``` /users/page.tsx
import { getUserList, getName } from "./api";
import { UserCard } from "./_user-card";
import { CustomSuspense } from "@/src/util/custom-suspense";
import Link from "next/link";

async function UserList() {
  const users = await getUserList();
  return (
    <ul className="list-inside list-disc border p-4">
      {users.map((user: { id: string; name: string }) => (
        <li key={user.id}>
          <Link href={`/users/${user.id}`}>{user.name}</Link>
        </li>
      ))}
    </ul>
  );
}

export default function Page() {
  return (
    <>
      <UserCard>
        名前：
        <CustomSuspense height={15} width={100}>
          {getName()}
        </CustomSuspense>
      </UserCard>
      <UserCard>
        <CustomSuspense height={350} width="100%">
          <UserList />
        </CustomSuspense>
      </UserCard>
    </>
  );
}

```
こちらのコードは、ユーザーの名前とユーザーのリストを表示しているソースです。
それぞれの部分では「CustomSuspense」に渡している「height」と「width」を変えて、スケルトンの表示部分の大きさを簡単に変更することができています。

![suspense.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/26d3e9da-ce73-9d9f-1c8e-d89d667f0a12.gif)

# どのように便利なのか
本題であるカスタムサスペンスを利用すると、どのように便利なのかを説明していきます。

### サイズ指定の自由度
場所によってローディング時の表示の大きさを変更したい場合に、「width」、「height」に渡す大きさを変えることで、スタイルの一貫性を保ちながら、柔軟に大きさを変更できます。
``` tsx
// 異なるサイズのローディング状態を簡単に実装
<CustomSuspense width={200} height={100}>
  <ExpensiveComponent />
</CustomSuspense>

// カードコンポーネント内での使用例
<div className="w-full">
  <CustomSuspense width="100%" height={300} className="rounded-lg" />
</div>
```
### スタイルのカスタマイズ
既存のスタイルに加えて、別のスタイルを加えたい場合もあるかと思います。
その時は、「className」に追加のスタイルを渡すことで、カスタマイズをすることも可能です。
``` tsx
// ページごとに異なるローディングスタイルを適用
<CustomSuspense 
  className="border" 
  width="100%" 
  height={150} 
/>

// 特定のセクション向けのローディングスタイル
<CustomSuspense 
  className="bg-gradient-to-r from-gray-200 to-gray-300" 
  width={250} 
  height={200} 
/>
```

### パフォーマンスと最適化
既存のReact SuspenseとTailwind CSSの利点を活かしながら、以下のような最適化を実現しています。

***1. clsxとtailwind-mergeによるクラス名の効率的な結合***
***2. アニメーションによる視覚的フィードバック***
***3. 最小限のオーバーヘッド***


### 拡張性
現在のバージョンでは基本的なカスタマイズに対応していますが、以下のような拡張をする対応も簡単にできます。

- ローディングアニメーションのカスタマイズ
- スケルトンUIの動的生成
- テーマに応じた自動スタイル変更

# まとめ
カスタムSuspenseコンポーネントは、単なるローディング表示以上の可能性を秘めています。アプリケーションの一貫性を保ちながら、柔軟なデザインと優れたユーザーエクスペリエンスを実現するための強力なツールとなるでしょう。

# 最後に
他にも色々な記事を書いているので、よければ読んでいってください！

https://qiita.com/hukuryo/items/b3b8f7b79c842b38e889

https://qiita.com/hukuryo/items/4a02784929b0cf0404a5

https://qiita.com/hukuryo/items/2a649d2ee018308d89f6

https://qiita.com/hukuryo/items/4e957621682a25da5f3b

https://qiita.com/hukuryo/items/e14779aeb0c0cf26c2d2