react-pdf/rendererを使って、作成したpdfを新規タブに表示する

ReactでPDFを作成し、それを新規タブに表示させる方法についてです。
react-pdfで検索した際に困ったため、記事にしてみようと思い立ちました。

# バージョン

- vite: 4.4.5
- React: 18.2.0
- TypeScript: 5.0.2
- react-pdf/renderer: 3.1.15

# コード

当初はchatGPTさんに手伝ってもらったものを改良して行きました。
ですが記事を書く際に公式ドキュメントを見ていると、BlobProviderなるものが提供されていたので、そちらを使用した例も自戒を込めて記載します。
ちゃんとドキュメント読んで♡

なお関数等の処理はカスタムフックとして他の処理と一緒に読み込もうとして少し苦戦したので、メモがてら以下コードもそのようになっております。

## 作成するPDF

適当に、一枚ずつ数字が書かれているpdfを作成します。
引数は`[1, 2, 3]`のようなnumberの配列を取るようになっています。

```PDF.tsx
import { Document, Page, Text } from "@react-pdf/renderer";

export const Pdf = ({ data }: { data: number[] }) => (
  <Document>
    {data.map((item) => (
      <Page key={item}>
        <Text>{item}</Text>
      </Page>
    ))}
  </Document>
);
```

## pdfを使用した方法

```index.tsx
import { Pdf } from "./Pdf";
import { PrintButton } from "./PrintButton";

function App () {
  return <PrintButton element={<Pdf data={[1, 2, 3, 4, 5]} />} />
};

export default App;
```

```PrintButton.tsx
import { usePrintButton } from "./usePrintButton";

export const PrintButton = ({ element }: { element: JSX.Element }) => {
  const { handlePrintPDF } = usePrintButton(element);

  return <button onClick={handlePrintPDF}>印刷する</button>;
};
```

```usePrintButton.ts
import { pdf } from "@react-pdf/renderer";

type UsePrintButton = {
  handlePrintPDF: () => void;
};

export const usePrintButton = (element: JSX.Element): UsePrintButton => {
  const handlePrintPDF = async () => {
    try {
      const blob = await pdf(element).toBlob();
      const url = URL.createObjectURL(blob);
      window.open(url, "_blank");
    } catch (error) {
      console.error("Error handing nameplate printing:", error);
    }
  };

  return { handlePrintPDF };
};
```
### 解説

#### そもそもblobとは何ぞ

*Blob = Binary Large OBject*
通常のデータ型に格納できないバイナリデータを格納するためのデータ型です。

JavaScriptでも同様にバイナリデータを扱うオブジェクトとして使用できるそうです。

ちなみに、このBlobへのアクセス先をURLとして表示させることを「Blob URL Schema」と呼ぶそうです。
このURLを作成しているのが、ここでは`URL.createObjectURL(blob)`となります。

#### usePrintButton.tsについて

pdfを利用して、受け取ったエレメントのblobデータを作成しています。
それを`URLcreateObjectURL`によりURLでアクセスできるようにし、`window.open`メソッドにより新規タブで該当urlを開くようになっています。

## BlobProviderを使用した方法

```index.tsx
import { UsingBlobProvider } from "./UsingBlobProvider";

function App() {
  return <UsingBlobProvider />
}

export default App;
```

```UsingBlobProvider.tsx
import { BlobProvider } from "@react-pdf/renderer";
import { Pdf } from "./Pdf";

export const UsingBlobProvider = () => {
  const handlePrintPdf = (url: string | null) => {
    if (url) {
      const newWindow = window.open(url, "_blank");
    } else {
      alert("URL is null");
    }
  };

  return (
    <>
      <BlobProvider document={<Pdf data={[1, 2, 3, 4, 5, 6, 7, 8]} />}>
        {({ url }) => (
          <div onClick={() => handlePrintPdf(url)}>
            <span>印刷する</span>
          </div>
        )}
      </BlobProvider>
    </>
  );
};

```

### 解説

#### BlobProvider

`BlobProvider`の`document`属性に作成したPDFを渡し、そのタグの中でchildrenであるurlをonClickの関数に渡しています。
こっちの方がスッキリしてますね()

childrenには他にもblobやloading, errorが使用出来るようです。

https://react-pdf.org/components#blobprovider

# 参考リンク

- BlobProvider

https://dev.to/jaymeeujj/how-to-generate-custom-pdf-using-react-and-react-pdf-6d4

- 公式ドキュメント

https://react-pdf.org/advanced#on-the-fly-rendering

- blobについて

https://memo.ag2works.tokyo/post-4332/

https://qiita.com/azu369yu/items/8998e1e1536a5acfb7b3