2024年 お世話になった React ライブラリをざっくり紹介

ミライトデザイン Advent Calendar 2024 16日目の記事になります。

https://qiita.com/advent-calendar/2024/miraito-inc

15日目は [takuma](https://qiita.com/tkek321) さんの [「DynamoDB 全くわからないのでまとめてみた」](https://qiita.com/tkek321/items/a86ccc21908a5836c472)の記事でした。

私も DynamoDB 全くわからない勢ですが、
RDBと比べて設計の考え方が全然違うんだなと感じました。
とても参考になりました！

# はじめに
現在、私は React を使用したプロジェクトに携わっています。
2024年も残りわずかとなり、今年お世話になったライブラリを振り返ってみることにしました。

# 目的
プロジェクトでは、自分以外のメンバーが導入したライブラリも多くあります。
そのため、これらのライブラリについて調べ、実際に試してみて、使い方をざっくり理解したいと考えています。

今回は、その中で紹介しやすかったり、便利だと思った5つのライブラリを紹介します。

# ライブラリ紹介について
以下の構成で説明を進めます。
- 実現したいこと
- ライブラリ名
- ライブラリ概要

## 実現したいこと： UI コンポーネントを効率的に構築したい
### ライブラリ名： Chakra UI 
https://v2.chakra-ui.com/getting-started

### ライブラリ概要
Chakra UIとは、React ベースの UI コンポーネントライブラリの一つで、
開発者が簡単かつ効率的にモダンな UI を構築できるよう設計されています。
特に、スタイルとテーマのカスタマイズが容易なため、デザインの一貫性を保ちながら柔軟な対応が可能です。

例として以下のようなデザインを作成してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360742/30d84a5b-a0f3-d361-beae-0b7e6de8e267.png)

実装例
```react
import { Box, Image, Text, Button, HStack, VStack } from "@chakra-ui/react";
import { FaCat } from "react-icons/fa6";

// ______________________________________________________
//

const Component = () => {
  return (
    <Box
      maxW="sm"
      borderWidth="1px"
      borderRadius="lg"
      overflow="hidden"
      p={6}
      bg="white"
      boxShadow="md"
    >
      <Image src="./images/cat1.jpeg" alt="Sample Image" />

      <Box mt={4} textAlign="center">
        <Text fontWeight="bold" fontSize="xl">
          サンプルカード
        </Text>
        <Text mt={2} color="gray.600">
          Chakra UI を使って簡単なレイアウトを作成してみる。
        </Text>
        <HStack spacing={8} justifyContent="center" p={4} mt={4}>
          <VStack spacing={2} align="center">
            <Text fontWeight="bold" fontSize="md" color="teal.500">
              サンプル1
            </Text>
            <Text fontSize="sm" color="gray.600">
              説明テキスト1
            </Text>
          </VStack>
          <VStack spacing={2} align="center">
            <Text fontWeight="bold" fontSize="md" color="teal.500">
              サンプル２
            </Text>
            <Text fontSize="sm" color="gray.600">
              説明テキスト2
            </Text>
          </VStack>
        </HStack>
        <Button colorScheme="teal" mt={6} leftIcon={<FaCat />}>
          ボタン
        </Button>
      </Box>
    </Box>
  );
};
```
いくつか特徴を挙げます
- 豊富なコンポーネント
    - ボタン、入力フィールド、モーダル、カードなど、日常的によく使用される基本コンポーネントが多数用意されています
- レイアウトの容易さ
    - クラス名を考える必要がなく、レイアウト用のコンポーネント（例: `HStack`, `VStack`）を使えば簡単に配置ができます
- スタイルのショートハンド
    - `p={6}`, `mt={4}` などの簡潔なスタイル指定が可能です
- カスタムテーマ機能
    - デフォルトのテーマをベースに、独自の色やフォントを簡単に設定できます
    - ブランドカラーやフォントサイズなどを指定したり、コンポーネントのスタイルを上書きすることが可能です
    - 参考記事は[こちら](https://dev.classmethod.jp/articles/chakra-ui-theme/)

※React の UI コンポーネントライブラリは、Chakra UI の他にも Material-UI, React Bootstrap など多く存在しますが、今回は Chakra UI に限定して紹介しました。

## 実現したいこと： 棒（円）グラフを描きたい
### ライブラリ名： recharts
https://recharts.org/en-US/

### ライブラリ概要
データの視覚化に便利なライブラリです。
例えば、以下のような「男女の年代比率」や「家計簿管理での収支のカテゴリ比率」を簡単に表示できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360742/50d38628-75a0-84f7-726a-cff0da5629bb.png)

実装例：年代比率の棒グラフ
```react
import { Box, Heading } from "@chakra-ui/react";
import {
  ResponsiveContainer,
  BarChart,
  CartesianGrid,
  XAxis,
  YAxis,
  Bar,
} from "recharts";

// ______________________________________________________
//

const data = [
  { name: "〜20代", value: 10 },
  { name: "30代", value: 40 },
  { name: "40代", value: 30 },
  { name: "50代〜", value: 5 },
];

const Component = () => {
  return (
    <Box p={4} boxShadow="md" mt={6}>
      <Heading as={"h3"} className="chart-heading" fontSize={"lg"} mb={3}>
        年代比率
      </Heading>
      <ResponsiveContainer width="100%" height={200}>
        {/* 棒グラフを表示するコンポーネント */}
        <BarChart data={data}>
          {/* グラフ背景の線を表示するグリッド */}
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis tickLine={false} dataKey="name" />
          <YAxis
            tickLine={false}
            type="number"
            tickFormatter={(value) => `${value}%`}
          />
          {/* 実際のデータを棒グラフとして描画 */}
          <Bar dataKey="value" fill="#DF6160" barSize={30} />
        </BarChart>
      </ResponsiveContainer>
    </Box>
  );
};
```
必要なデータがあれば、棒グラフ (BarChart) を簡単に作成できます。
また、棒グラフだけでなく、円グラフなら `PieChart` コンポーネントを使用できます。

## 実現したいこと： 画像一覧から写真を選択・拡大表示し、次の写真に移動させたい
### ライブラリ名： yet-another-react-lightbox
https://www.npmjs.com/package/yet-another-react-lightbox/v/1.1.1

### ライブラリ概要
写真の拡大表示に加え、次の写真への移動が必要なため適したライブラリを検討しました。
候補は`lightbox-react`と`yet-another-react-lightbox`の2つでしたが、ダウンロード数と更新頻度の比較から後者を選択しました。

[ダウンロード数比較はこちら](https://npmtrends.com/lightbox-react-vs-yet-another-react-lightbox)

機能説明
- 画像をクリックすると、モーダルとして拡大表示されます
- モーダル内では矢印ボタンで次の画像に移動可能
- 閉じるボタンで元の画面に戻ります

※公式サンプルでは、ボタンでモーダルを開く例の記述がありましたが、
この記事では画像クリックでモーダルを開くように実装しています。

![lightbox.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360742/d84c4b90-53d9-f98c-997b-4e62167be09b.gif)

実装例
```react
import { useState } from "react";
import Lightbox from "yet-another-react-lightbox";
import "yet-another-react-lightbox/styles.css";
import { SimpleGrid, Box, Image } from "@chakra-ui/react";

// ______________________________________________________
//

// 表示する画像の配列
const images = [
  "cat1.jpeg",
  "cat2.jpeg",
  "cat3.jpeg",
  "cat4.jpeg",
  "cat5.jpeg",
  "cat6.jpeg",
];

const Component = () => {
  const [isOpen, setIsOpen] = useState(false); // Lightboxの表示状態
  const [currentIndex, setCurrentIndex] = useState(0); // 現在表示している画像のインデックス

  // 画像クリック時の処理
  const handleClick = (index) => {
    setCurrentIndex(index);
    setIsOpen(true);
  };

  return (
    <Box p={4} mt={50} mb={150}>
      {/* 画像一覧のグリッド表示 */}
      <SimpleGrid columns={2} spacing={4}>
        {images.map((src, index) => (
          <Image
            key={index}
            src={`./images/${src}`}
            alt={`image-${index}`}
            cursor="pointer"
            onClick={() => handleClick(index)}
            borderRadius="md"
            boxShadow="md"
          />
        ))}
      </SimpleGrid>
      
      {/* Lightbox モーダル */}
      <Lightbox
        open={isOpen} // モーダルの開閉状態
        index={currentIndex} // 現在表示している画像インデックス
        close={() => setIsOpen(false)}
        slides={images.map((image) => ({
          src: `./images/${image}`,
          alt: "image",
        }))}
      />
    </Box>
  );
};
```

## 実現したいこと： 画面のスクロールに応じた要素の表示制御
### ライブラリ名： react-intersection-observer
https://www.npmjs.com/package/react-intersection-observer

### ライブラリ概要
フォーム登録ページでは、「登録ボタン」を固定フッターとして画面下部に表示していました。
しかし、ページの最下部までスクロールすると、最下部の要素が登録ボタンに隠れてしまう問題が発生。

そこで、画面のスクロール位置に応じて、最下部の登録ボタンが表示されたタイミングで固定フッターの登録ボタンを非表示にするよう調整しました。

機能説明
- 最初は固定フッターの「登録ボタン1」が表示されています
- 画面下部の「登録ボタン2」が表示されたタイミングで固定フッター（登録ボタン1）が非表示になり、下部の要素が隠れないようになります

イメージ
![IntersectionObserver.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360742/e9376e0f-2258-fdaa-0aa0-fe3c0ac7d4ee.gif)


実際例：コード
```react
import styled from "styled-components";
import { useInView } from "react-intersection-observer";

// ______________________________________________________
//

const Component = ({ className }) => {
  // ref: 監視対象の要素参照
  // inView: 監視対象が画面内に入っているかどうかの判定フラグ
  const [ref, inView] = useInView({
    threshold: 0.1, // 要素の10%以上が表示されたときに検知
  });

  return (
    <div className={className}>
      <div className="content">
        <div className="section">セクション 1</div>
        <div className="section">セクション 2</div>
        <div className="section">セクション 3</div>
        <div className="section">セクション 4</div>
        {/* 監視されているボタン */}
        {/* refを与えた要素がウインドウ内に現れるとinViewがtrueになる */}
        {!inView && <div className="fixed-button">登録ボタン1</div>}
        {/* 監視対象のボタン */}
        <button ref={ref} className="button">
          登録ボタン2
        </button>
        <div className="section">フッター</div>
      </div>
    </div>
  );
};

// ______________________________________________________
//
const StyledComponent = styled(Component)`
  .content {
    padding: 20px;
  }

  .section {
    padding: 80px;
    background: #f0f0f0;
    margin: 20px 0;
  }

  .fixed-button {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    padding: 10px 20px;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    z-index: 100; /* フッターより前に表示させる */
    width: 300px;
    text-align: center;
  }
  .footer {
    display: none;
    padding: 20px;
    background: #333;
    color: white;
    text-align: center;
    margin-top: 20px;
  }
  .button {
    padding: 10px 20px;
    background: green;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    width: 100%;
    color: white;
    font-size: 16px;
  }
`;
```

## 実現したいこと： 画像をカルーセル表示したい
### ライブラリ名： embla-carousel-react
https://www.npmjs.com/package/embla-carousel-react

### ライブラリ概要
画像をカルーセル形式で表示するために使用したライブラリです。
カルーセルは、限られたスペースで多くの画像を見せたい場合などに有効ですね。

ユーザーはボタンや画像のサムネイルをクリックすることで、
スライドを切り替えることができます。

また、画像の枚数を（2/10など）表示したり、自動で画像が切り替わるような設定も可能です。

![slider.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360742/76299b99-d358-d5e7-a2e6-ccb17fcf56e9.gif)

実際のコード例（スタイルは省略）
```react
import styled from "styled-components";
import useEmblaCarousel from "embla-carousel-react";
import { useCallback } from "react";

// ______________________________________________________
//

// 表示する画像リスト
const images = [
  "cat1.jpeg",
  "cat2.jpeg",
  "cat3.jpeg",
  "cat4.jpeg",
  "cat5.jpeg",
  "cat6.jpeg",
];

const Component = ({ className }) => {
  // emblaRef: スライダーの DOM 要素に関連付ける参照 (ref)。
  // emblaApi: スライダー操作用の API オブジェクト。スライドの移動、状態管理に使用する
  const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }); // スライダーの無限ループを有効にする
  // 前のスライドへ移動
  const onPrevButtonClick = useCallback(() => {
    if (!emblaApi) return;
    emblaApi.scrollPrev();
  }, [emblaApi]);
  
  // 次のスライドへ移動
  const onNextButtonClick = useCallback(() => {
    if (!emblaApi) return;
    emblaApi.scrollNext();
  }, [emblaApi]);

  return (
    <div className={className}>
      <div className="slider">Slider</div>
      {/* スライダー本体 */}
      <div className="embla" ref={emblaRef} style={{ overflow: "hidden" }}>
        <div className="embla__container" style={{ display: "flex" }}>
          {images.map((src, index) => (
            <div
              className="embla__slide"
              key={index}
              style={{ flex: "0 0 100%" }}
            >
              <img
                src={`./images/${src}`}
                alt={`Slide ${index + 1}`}
                style={{ width: "100%", height: "auto" }}
              />
            </div>
          ))}
        </div>
      </div>
      {/* スライド操作ボタン */}
      <div className="embla__controls">
        <div className="embla__buttons">
          <button onClick={onPrevButtonClick}>Prev</button>
          <button onClick={onNextButtonClick}>Next</button>
        </div>
      </div>
    </div>
  );
};

// ______________________________________________________
//

const StyledComponent = styled(Component)`
  .slider {
    font-size: 32px;
    padding: 10px;
    text-align: center;
  }

  .embla {
    overflow: hidden;
  }
  .embla__container {
    display: flex;
  }
  .embla__slide {
    flex: 0 0 100%;
    min-width: 0;
  }
  .embla__buttons {
    width: 100%;
    margin: 0 auto;
    display: flex;
    gap: 20px;
    justify-content: center;
  }
  button {
    background-color: #fff;
    // border: 1px solid #000;
    // background-color: #bce2e8;
    border: none;
    padding: 10px;
    width: 100px;
    font-weight: bold;
    font-size: 24px;
  }
`;
```

# さいごに
この記事では5つの React ライブラリを紹介しました。

本当はもっと多くのライブラリを紹介する予定だったのですが、
調べたり整理するのに予想以上に時間がかかってしまい、紹介しきれませんでした。

また機会があれば、別の記事で他のライブラリも紹介したいと思います。

この記事がきっかけで、新たに使い始めるライブラリが見つかれば嬉しいです！

明日は、[yuki](https://zenn.dev/yyykms123) さんの記事です。
よろしくお願いします！

# 参考
https://react-icons.github.io/react-icons/icons/fa6/

https://zenn.dev/terrierscript/books/2021-05-chakra-ui/viewer/0-1-1-intro