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?

【React】react-pdfでポートフォリオMATRIXFLOWにpdf作成機能を実装してみた

Posted at

本記事の内容について

ポートフォリオ「MATRIXFLOW」の機能拡充としてpdf作成機能を実装しリリースしたので、本記事に投稿いたします!

実装Before/After

実装前

マトリクスの横にあるサイドバー風ドキュメントに、マトリクスの内容が自動で反映されます。
matrixflow_qiita.gif

実装後

サイドバー風ドキュメント部分にpdfプレビューが表示され、ダウンロードできるようにしました。
matrixflow_qiita_function_5_pdf_generation.gif

実装方法について

使用技術

  • React 18.3.1
    • Redux
    • react-pdf/renderer 4.0.0
  • Vite 5.4.8

実装手順

react-pdf/renderer のインストール

公式を参考にreact-pdf/rendererをインストールします。

npmを使っているため下記コマンドにて

terminal
npm install @react-pdf/renderer --save

公式のサンプルpdfを表示できるようにする

まず、公式のサンプルpdfと同じ内容をアプリ上で表示できるかトライしました。

pdfを表示するページコンポーネントCreateMatrixFlowPage.jsx上の設定

  • PDFViewerを設定
  • pdfとして表示するコンポーネントをDocumentPDFコンポーネントとする
src/resourses/js/Components/Pages/CreateMatrixFlowPage.jsx
import React from 'react';

// PDFViewerコンポーネントをreact-pdf/rendererからインポート
import { PDFViewer } from '@react-pdf/renderer';

// コンポーネントの読み込み
import DocumentPDF from '../Components/DocumentPDF';

const CreateMatrixFlowPage = () => {
    return (
        <>
            <div className="sidebar-on-PDFViewerMode">
                <PDFViewer style={{ width: '100%', height: '80vh' }}>
                    // pdf表示したいコンポーネント(DocumentPDF)をPDFViewerでラップ
                    <DocumentPDF/>
                </PDFViewer>
            </div>
        </>
    );
};

export default CreateMatrixFlowPage;

DocumentPDFコンポーネントの設定

公式のサンプルpdfに対してImageコンポーネントを除外した内容をDocumentPDFコンポーネントへ

公式のサンプルpdfは下記のように画像パスが設定されていますが、MATRIXFLOWでも同様にこのパスに画像ファイルを配置しないとエラーになるため除外しました。

<Image
  style={styles.image}
  src="/images/quijote1.jpg"
/>
src/resourses/js/Components/DocumentPDF.jsx
import React from 'react';
import { Page, Text, Font, View, Document, StyleSheet } from '@react-pdf/renderer';

const DocumentPDF = () => (
  <Document>
    <Page style={styles.body}>
      <Text style={styles.header} fixed>
        ~ Created with react-pdf ~
      </Text>
      <Text style={styles.title}>Don Quijote de la Mancha</Text>
      <Text style={styles.author}>Miguel de Cervantes</Text>
      // ..省略(公式のサンプルpdf内容とほぼ同様、Imageは除外)
    </Page>
  </Document>
);

Font.register({
  family: 'Oswald',
  src: 'https://fonts.gstatic.com/s/oswald/v13/Y_TKV6o8WovbUd3m_X9aAA.ttf'
});

const styles = StyleSheet.create({
  body: {
    paddingTop: 35,
    paddingBottom: 65,
    paddingHorizontal: 35,
  },
  title: {
    fontSize: 24,
    textAlign: 'center',
    fontFamily: 'Oswald'
  },
  author: {
    fontSize: 12,
    textAlign: 'center',
    marginBottom: 40,
  },
  subtitle: {
    fontSize: 18,
    margin: 12,
    fontFamily: 'Oswald'
  },
  text: {
    margin: 12,
    fontSize: 14,
    textAlign: 'justify',
    fontFamily: 'Times-Roman'
  },
  image: {
    marginVertical: 15,
    marginHorizontal: 100,
  },
  header: {
    fontSize: 12,
    marginBottom: 20,
    textAlign: 'center',
    color: 'grey',
  },
  pageNumber: {
    position: 'absolute',
    fontSize: 12,
    bottom: 30,
    left: 0,
    right: 0,
    textAlign: 'center',
    color: 'grey',
  },
});

export default DocumentPDF;

公式サンプルのpdfが表示されることを確認

公式サンプルのpdfが表示されることを確認。(ここまでは順調...だった...)
image.png

バグ対処

公式サンプルpdfが表示できて順調と思いきや、いろいろバグ対処に見舞われたので記します...😭

バグ対処①:Reduxから参照した内容をpdfに表示できない

実装した内容

サイドバー風のドキュメントは、Reduxを使ってstoreに保持された状態を参照した内容を表示しています。
DocumentPDFコンポーネントに対して同様にReduxによりstoreの状態を参照しようとしました。
コードは次のようにしました。

src/resourses/js/Components/DocumentPDF.jsx
import React from 'react';
import { useSelector } from 'react-redux';

import { Page, Text, Font, View, Document, StyleSheet } from '@react-pdf/renderer';

const DocumentPDF = () => {
 // Reduxからworkflowsを参照
      const workflows = useSelector((state) => state.workflows.workflow);
  // Reduxから参照したworkflowsからなる定数
      const workflowName = workflows[0]?.name;

  <Document>
    <Page style={styles.body}>
      <Text style={styles.header} fixed>
        ~ Created with react-pdf ~
      </Text>

      // Reduxから参照したworkflowsからworkflowNameが表示できるかトライ
      <Text style={styles.title}>{workflowName}</Text>
    </Page>
  </Document>
};

Font.register({
  family: 'Oswald',
  src: 'https://fonts.gstatic.com/s/oswald/v13/Y_TKV6o8WovbUd3m_X9aAA.ttf'
});

const styles = StyleSheet.create({
// ...(省略)
});

export default DocumentPDF;

バグ発生

...がエラー発生(下記)によりReduxから参照できませんでした。

console
CreateMatrixFlowPage-0nmkX1Hh.js:218 TypeError: Cannot destructure property 'store' of 't(...)' as it is null.

対処

Reduxに参照するのではなく、親コンポーネントから参照したい内容をpropsとして渡すことでエラー発生せず表示することができました。

src/resourses/js/Componets/Pages/CreateMatrixFlowPage.jsx
import React from 'react';
import { useSelector } from 'react-redux';
import { PDFViewer } from '@react-pdf/renderer';

const CreateMatrixFlowPage = () => {
    // DocumentPDFコンポーネントに表示したいstateをReduxから参照
        const flowsteps = useSelector((state) => state.flowsteps);
        const checklists = useSelector((state) => state.checkLists);
        const workflows = useSelector((state) => state.workflow);
        const workflowId = useSelector((state) => state.workflow.workflowId);

    return (
        <div>
            <PDFViewer style={{ width: '100%', height: '80vh' }}>
                <DocumentPDF
                  // propsとしてDocumentPDFコンポーネントに渡す
                    flowsteps={flowsteps}
                    checklists={checklists}
                    workflowId={workflowId}
                    workflows={workflows}
                />
            </PDFViewer>
        </div>
    );
};

export default CreateMatrixFlowPage;

バグ対処②:日本語で入力した内容が文字化けする

実装内容

DocumentPDFコンポーネントに、pdfのタイトルに日本語をハードコーディング
(タイトルとして「申請」)

src/resourses/js/Components/DocumentPDF.jsx
import React from 'react';
import { useSelector } from 'react-redux';

import { Page, Text, Font, View, Document, StyleSheet } from '@react-pdf/renderer';

const DocumentPDF = () => {
  <Document>
    <Page style={styles.body}>
      <Text style={styles.header} fixed>
        ~ Created with react-pdf ~
      </Text>

      // 試しにタイトルとして「申請」と表示させたい
      <Text style={styles.title}>申請</Text>
    </Page>
  </Document>
};

Font.register({
  family: 'Oswald',
  src: 'https://fonts.gstatic.com/s/oswald/v13/Y_TKV6o8WovbUd3m_X9aAA.ttf'
});

const styles = StyleSheet.create({
// ...(省略)
});

export default DocumentPDF;

バグ発生

文字化けする。
image.png

対処

src/resourses/js/Components/DocumentPDF.jsx
import React from 'react';
import { Page, Text, Font, View, Document, StyleSheet } from '@react-pdf/renderer';

// Nasuフォントのインポート
import fontRegular from '../../assets/fonts/Nasu-Regular.ttf'; //Nasuフォントを保存したパスを指定
import fontBold from '../../assets/fonts/Nasu-Bold.ttf'; //Nasuフォントを保存したパスを指定        

const DocumentPDF = () => {
  return (
    <Document>
      <Page style={styles.body}>
        <Text style={styles.title}>申請</Text>
      </Page>
    </Document>
  );
};

// Nasuフォントを登録
Font.register({
  family: 'Nasu-Regular',
  src: fontRegular
});

Font.register({
  family: 'Nasu-Bold',
  src: fontBold
});

// Nasuフォントを指定
const styles = StyleSheet.create({
  body: { padding: 30 },
  title: { 
      fontSize: 24, textAlign: 'center', marginBottom: 10, 
      fontFamily: 'Nasu-Bold'  //font-familyに'Nasu-Bold'を指定
  },
});

export default DocumentPDF;

参考記事

下記の記事を参考にNasuフォントを使用いたしました。

終わりに

自分のスタイリングしたサイドバー風ドキュメントだと、本格的なドキュメントができている感じがあまりしないなぁ、と物足りなかったのですがpdf作成機能を実装して本格的に近づき嬉しいです!

もう1つのポートフォリオ「PlotForge」について

もう1つポートフォリオ「PlotForge」もリリースしております!
こちらもご覧いただけますと幸いです。

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?