React で PDF ファイルを作成する方法
React アプリケーションから PDF ドキュメントを作成するチュートリアルへようこそ! このチュートリアルでは、PDF を生成するさまざまなライブラリを探索し、人気のjsPDFライブラリを使用して React コンポーネントから直接 PDF ファイルを作成する方法を学びます。 それでは、始めましょう!
PDF(Portable Document Format)は、ドキュメントのレイアウトとフォーマットを保持しながら共有および印刷するために広く使用されているファイル形式です。 React の Web 開発者として、請求書、レポート、販売契約などの PDF ドキュメントを React アプリケーションから直接生成する必要があるシナリオに遭遇するかもしれません。
React PDF ライブラリの選択
React アプリケーションで PDF ドキュメントを作成することは、特に新しいフィールドの場合は大変な作業です。 幸いなことに、このプロセスを大幅に簡素化するサードパーティのライブラリがいくつかあります。 各ライブラリには独自の機能とユーティリティが用意されており、さまざまなユースケースに対応しています。 これらのライブラリについてさらに詳しく見ていきましょう。
jsPDF
jsPDFは、JavaScriptからPDFファイルを生成するために開発者の間で広く人気のあるライブラリです。 主なセールスポイントの 1 つはそのシンプルさです。 その構文と使用法は非常に簡単で、HTML コンテンツをすぐに PDF ファイルに変換できます。
フォントのサイズや色の変更、ページの向きやサイズの調整など、PDF の書式設定やレイアウトを制御できます。jsPDF は、ブラウザ環境とサーバー環境の両方で機能する堅牢なソリューションであり、幅広い JavaScript アプリケーションに最適な選択肢です。
pdfmake
pdfmakeは、純粋な JavaScript で作成されたクライアント/サーバー側の PDF 印刷ソリューションとして際立っています。 このライブラリは、包括的な API と柔軟なレイアウトオプションのおかげで、より複雑な PDF を作成するための優れた選択肢です。 pdfmakeを使用すると、シンプルな JavaScript オブジェクトを使用してドキュメントのコンテンツと構造を定義し、それを有効な PDF ドキュメントに変換できます。
React-PDF
React-PDFは、React コンポーネントを使用して PDF ファイルを作成するための強力な機能を提供するユニークなライブラリです。 JavaScript オブジェクトでドキュメント構造を手動で記述する代わりに、再利用可能なコンポーネントとプロップスを使用して、通常の React アプリケーションを構築するように PDF を作成できます。 IronPDF の Web サイトには、React-PDF ライブラリを使用した PDF の作成に関するチュートリアルがあります。
jsPDF を選ぶ理由
3 つのライブラリすべてが React で PDF ドキュメントを生成するための強力なツールを提供しますが、このチュートリアルでは、シンプルで柔軟性があり、コミュニティで広く採用されているため、jsPDF を使用します。 初心者にとって参入障壁が低く、その堅牢な機能セットにより多くのユースケースに適した選択となります。 jsPDFで調査する原則は、PDF を生成するための確固たる基盤を提供し、プロジェクトが要求する場合は、他のライブラリをより簡単に取り上げることができます。
前提条件
このチュートリアルに飛び込む前に、スムーズに進めるために必要なツールと知識が十分に装備されていることを確認することが重要です。 このチュートリアルの前提条件は次のとおりです。
React の基本的な理解
まず第一に、単一ページ アプリケーションを構築するための人気のある JavaScript ライブラリである React の基本的な理解が必要です。 React の JSX(JavaScript XML)、コンポーネント、状態、プロパティといった概念に精通している必要があります。
開発環境
また、React アプリケーションを構築するための開発環境をコンピューターに設定しておく必要があります。 これには、テキストエディタまたは統合開発環境 (IDE) が含まれます。 Visual Studio Code、Atom、Sublime Text などのテキスト エディターはすべて優れたオプションです。
Node.js と npm
プロジェクトとその依存関係を管理するために、Node.js および npm (Node Package Manager) を使用します。 コンピューターに Node.js がインストールされていることを確認してください。 Node.jsは、サーバーでJavaScriptを実行するためのJavaScriptランタイムです。 これには npm がインストールされているので、プロジェクトに必要なライブラリを管理できます。
次の端末コマンドを実行して Node.js と npm がインストールされているかどうかを確認できます。
node -v
npm -vnode -v
npm -vこれらのコマンドは、システムにインストールされている Node.js および npm のバージョンを表示します。 インストールされていない場合やバージョンが古い場合は、公式ダウンロードページから最新の長期サポート (LTS) バージョンの Node.js をダウンロードしてインストールする必要があります。
ステップ 1: プロジェクトのセットアップ
React プロジェクトのセットアップを開始しましょう。 ターミナルを開き、プロジェクトを作成したいディレクトリに移動します。 次のコマンドを実行して、新しい React アプリケーションを作成します。
npx create-react-app pdf-from-reactnpx create-react-app pdf-from-react
このコマンドは、pdf-from-reactという名前の新しいディレクトリを作成し、基本的な React プロジェクト構造を持ちます。
次に、プロジェクトディレクトリに移動します。
cd pdf-from-reactcd pdf-from-reactこれで、コードエディタでプロジェクトを開いて実装を進めることができます。
ステップ 2: 必要な依存関係の追加
まず、必要なパッケージをインストールする必要があります。 次のターミナルコマンドを使用して、react、react-dom、@mui/material、jspdfをインストールします。
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-materialnpm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-materialステップ 3: PDF 生成機能の構築
ライブラリのインポート
アプリケーションに必要な依存関係をインポートすることから始めます。 これには、Material-UI ライブラリのさまざまなコンポーネント、PDF を生成するための jsPDF ライブラリ、およびスタイリングユーティリティが含まれます。
import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";スタイル付きコンポーネントの作成
アプリに一貫性のあるクロスブラウザー動作を追加するために、MUI ライブラリの styled ユーティリティを使用して StyledTableCell と StyledTableRow を作成しています。
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));App コンポーネントの作成
このアプリケーションのメインコンポーネントはAppコンポーネントです。 ここでは 4 つの状態変数を使用しています。カスタマーのデータを追跡するための customerName と customerAddress、インボイスのアイテムリストを追跡するための items、必要に応じてエラーメッセージを表示するための error です。
function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);ユーザー入力の処理
このコードブロックでは、ユーザーの操作を処理するための関数を定義しました: アイテムの詳細を変更し、新しいアイテムを追加し、アイテムを削除します。 handleItemChange関数は、ユーザーがプロパティを変更した場合にアイテムのプロパティを更新します。 addItem 関数は、リストに新しいアイテムを追加します。deleteItem 関数は、アイテムをリストから削除します。
const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};インボイスの生成
次に示すのは、generateInvoice 関数のコードです。
// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName ||
!customerAddress ||
items.some((item) => !item.name || !item.quantity || !item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName ||
!customerAddress ||
items.some((item) => !item.name || !item.quantity || !item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};generateInvoice 関数では、まず入力フィールドの検証を実行し、顧客名、顧客住所、およびアイテムの詳細が入力されていることを確認します。 これらのフィールドのいずれかが空の場合は、error 状態を true に設定して早期に返します。
次に、new jsPDF("p", "pt") を呼び出して jsPDF の新しいインスタンスを作成します。 最初の引数 "p" はページの向きを縦方向、2 番目の引数 "pt" は測定単位をポイントとして指定します。
次に、PDF ドキュメントにコンテンツを追加し始めます。 doc.setFontSize を使用してフォントサイズを設定し、doc.text メソッドを使用してページの特定の座標にテキストを追加します。 インボイスのヘッダーに、タイトル、インボイス番号、日付、顧客名、顧客住所を追加します。
ヘッダーの後、フォントサイズを設定し、doc.line を使用してヘッダーからアイテムを分離する線を追加することにより、"アイテム" セクションを追加します。 次に、items 配列内の各アイテムを反復処理し、数量に価格を掛けて各アイテムの合計価格を計算します。total 変数をすべてのアイテム合計の合計で更新します。
各アイテムについて、doc.text を使用してアイテムの名前、数量、価格、およびアイテム合計を PDF ドキュメントに追加します。 各アイテムの次の行に移動するために、yOffset 変数をインクリメントします。 最後に、アイテムを合計から分離する線を追加し、doc.text を使用してドキュメントの右下に合計金額を追加します。
コンテンツが追加されたら、doc.save("invoice.pdf") を使用して、生成された PDF をユーザーのコンピュータに "invoice.pdf" として保存します。 最後に、以前のバリデーション エラーをクリアするために、error 状態を false にリセットします。
ステップ 4: UI のレンダリング
return ステートメントには、レンダリングプロセスを処理する JSX コードが含まれています。 これには、顧客名と住所の入力フィールド、アイテムの詳細を入力するためのテーブル、アイテムを追加してインボイスを生成するためのボタン、およびバリデーションエラーを表示するためのエラースナックバーが含まれています。
それはButton、TextField、Box、Container、Typography、Table、TableBody、TableCell、TableContainer、TableHead、TableRow、Paper、IconButton、Snackbar、およびAlertなどのMaterial-UIライブラリから構成要素を使用して、基本的な構成要素を作成します。 これらのコンポーネントは、フォームフィールド、テーブル、ボタン、およびエラーメッセージを作成するために使用されます。
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);完全な App.js および App.css コード
ここにプロジェクトにコピーして貼り付けることができる完全な App.js コードがあります。
import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));
function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);
// Event handler for item changes
const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
// Add new item to the list
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
// Delete an item from the list
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName ||
!customerAddress ||
items.some((item) => !item.name || !item.quantity || !item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);
}
export default App;import React, { useState } from "react";
import "./App.css";
import {
Button,
TextField,
Box,
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Snackbar,
Alert,
} from "@mui/material";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import jsPDF from "jspdf";
import { styled } from "@mui/material/styles";
import { tableCellClasses } from "@mui/material/TableCell";
const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
[`&.${tableCellClasses.body}`]: {
fontSize: 14,
},
}));
const StyledTableRow = styled(TableRow)(({ theme }) => ({
"&:nth-of-type(odd)": {
backgroundColor: theme.palette.action.hover,
},
"&:last-child td, &:last-child th": {
border: 0,
},
}));
function App() {
// State variables
const [customerName, setCustomerName] = useState("");
const [customerAddress, setCustomerAddress] = useState("");
const [items, setItems] = useState([{ name: "", quantity: "", price: "" }]);
const [error, setError] = useState(false);
// Event handler for item changes
const handleItemChange = (index, event) => {
let newItems = [...items];
newItems[index][event.target.name] = event.target.value;
setItems(newItems);
};
// Add new item to the list
const addItem = () => {
setItems([...items, { name: "", quantity: "", price: "" }]);
};
// Delete an item from the list
const deleteItem = (index) => {
let newItems = [...items];
newItems.splice(index, 1);
setItems(newItems);
};
// Generate invoice
const generateInvoice = () => {
// Validate the input fields
if (
!customerName ||
!customerAddress ||
items.some((item) => !item.name || !item.quantity || !item.price)
) {
setError(true);
return;
}
// Create a new jsPDF instance
let doc = new jsPDF("p", "pt");
// Add invoice header
doc.setFontSize(24);
doc.text("Invoice", 40, 60);
doc.setFontSize(10);
doc.text("Invoice Number: 123456", 40, 90);
doc.text("Date: " + new Date().toDateString(), 40, 110);
doc.text(`Customer Name: ${customerName}`, 40, 130);
doc.text(`Customer Address: ${customerAddress}`, 40, 150);
// Add items section
doc.setFontSize(14);
doc.text("Items:", 40, 200);
doc.line(40, 210, 550, 210);
// Add item details
doc.setFontSize(12);
let yOffset = 240;
let total = 0;
items.forEach((item) => {
let itemTotal = item.quantity * item.price;
total += itemTotal;
doc.text(`Item: ${item.name}`, 40, yOffset);
doc.text(`Quantity: ${item.quantity}`, 200, yOffset);
doc.text(`Price: $${item.price}`, 300, yOffset);
doc.text(`Total: $${itemTotal}`, 400, yOffset);
yOffset += 20;
});
// Add total
doc.line(40, yOffset, 550, yOffset);
doc.setFontSize(14);
doc.text(`Total: $${total}`, 400, yOffset + 30);
// Save the generated PDF as "invoice.pdf"
doc.save("invoice.pdf");
// Reset error state
setError(false);
};
return (
<Container maxWidth="md">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom>
Create Invoice
</Typography>
{/* Customer Name and Address fields */}
<Grid container spacing={3}>
<Grid item xs={6}>
<TextField
label="Customer Name"
fullWidth
margin="normal"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Customer Address"
fullWidth
margin="normal"
value={customerAddress}
onChange={(e) => setCustomerAddress(e.target.value)}
/>
</Grid>
</Grid>
{/* Items table */}
<TableContainer component={Paper}>
<Table sx={{ minWidth: 700 }} aria-label="invoice table">
<TableHead>
<TableRow>
<StyledTableCell>Item Name</StyledTableCell>
<StyledTableCell align="left">Quantity</StyledTableCell>
<StyledTableCell align="left">Price</StyledTableCell>
<StyledTableCell align="left">Action</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item, index) => (
<StyledTableRow key={index}>
<StyledTableCell component="th" scope="row">
<TextField
fullWidth
value={item.name}
onChange={(event) => handleItemChange(index, event)}
name="name"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.quantity}
onChange={(event) => handleItemChange(index, event)}
name="quantity"
/>
</StyledTableCell>
<StyledTableCell align="right">
<TextField
fullWidth
value={item.price}
onChange={(event) => handleItemChange(index, event)}
name="price"
/>
</StyledTableCell>
<StyledTableCell align="right">
<IconButton onClick={() => deleteItem(index)}>
<DeleteIcon />
</IconButton>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{/* Buttons */}
<Box mt={2} display="flex" gap={2}>
<Button variant="contained" onClick={addItem}>
Add Item
</Button>
<Button variant="outlined" color="success" onClick={generateInvoice}>
Generate Invoice
</Button>
</Box>
</Box>
{/* Error Snackbar */}
<Snackbar
open={error}
autoHideDuration={6000}
onClose={() => setError(false)}
anchorOrigin={{ vertical: "top", horizontal: "right" }}
>
<Alert onClose={() => setError(false)} severity="error">
Please fill in all required fields.
</Alert>
</Snackbar>
</Container>
);
}
export default App;ここに App.css コードがあります。
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
/* This is the weight for bold in Poppins */
color: #FF6347;
/* This is the color Tomato. Replace with your preferred color */
}
body {
font-family: 'Poppins', sans-serif;
background-color: #E9F8F4;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}ステップ 5: アプリケーションのテスト
PDF 生成機能をテストするには、端末で次のコマンドを実行します。
npm startnpm startこれにより、開発サーバーが開始され、ブラウザで http://localhost:3000 にアプリケーションを表示できます。

入力フィールドに顧客名、住所、アイテムの詳細を入力し、"Generate Invoice"ボタンをクリックします。 PDF ファイルはコンピュータにダウンロードされ、開いて生成された請求書のフルページ表示を確認できます。

"Generate Invoice" ボタンをクリックすると、PDF ファイルが生成されます。

空白のフィールドで PDF を生成しようとすると、右上隅にエラーメッセージが表示されます。

IronPDF - Node.js PDF ライブラリ
IronPDF for Node.js は、正確性、使いやすさ、速度に優れた包括的な Node.js PDF ライブラリです。 HTML、URL、およびReactの画像から直接PDFを生成、編集、フォーマットするための広範な機能を提供します。 Windows、MacOS、Linux、Docker、AzureやAWSなどのクラウドプラットフォームを含むさまざまなプラットフォームをサポートしているため、IronPDFはクロスプラットフォームの互換性を保証します。 直観的なAPIを備えているため、開発者は迅速にPDF生成と操作をNode.jsプロジェクトに統合することができます。
IronPDF Node.js の注目すべき機能には、ピクセル単位のレンダリング、広範なフォーマットオプション、PDF のマージや分割、注釈の追加、PDF フォームの作成などの高度な編集機能が含まれます。
HTML ファイル、HTML ストリング、および URL から PDF ドキュメントを生成する例を以下に示します。
import { PdfDocument } from "@ironsoftware/ironpdf";
(async () => {
const pdfFromUrl = await PdfDocument.fromUrl("https://getbootstrap.com/");
await pdfFromUrl.saveAs("website.pdf");
const pdfFromHtmlFile = await PdfDocument.fromHtml("design.html");
await pdfFromHtmlFile.saveAs("markup.pdf");
const pdfFromHtmlString = await PdfDocument.fromHtml("<p>Hello World</p>");
await pdfFromHtmlString.saveAs("markup_with_assets.pdf");
})();import { PdfDocument } from "@ironsoftware/ironpdf";
(async () => {
const pdfFromUrl = await PdfDocument.fromUrl("https://getbootstrap.com/");
await pdfFromUrl.saveAs("website.pdf");
const pdfFromHtmlFile = await PdfDocument.fromHtml("design.html");
await pdfFromHtmlFile.saveAs("markup.pdf");
const pdfFromHtmlString = await PdfDocument.fromHtml("<p>Hello World</p>");
await pdfFromHtmlString.saveAs("markup_with_assets.pdf");
})();PDF に関連するタスクに関する詳細なコード例については、この IronPDF コード例のページをご覧ください。
結論
結論として、React アプリケーションでの PDF の作成は威圧的である必要はありません。 適切なツールと明確な理解があれば、美しい構造化された PDF ドキュメントを簡単に生成できます。 各自に強みと独自の機能を持つ、jsPDF、pdfmake、React-PDF など、さまざまなライブラリを調査しました。
IronPDF のフレームワーク用の簡単な統合プロセスとライブラリに関する素晴らしいドキュメント、そして応答性の高い技術サポートにより、開発者は迅速に立ち上げることができ、Node.js アプリケーションでプロフェッショナル品質の PDF を生成するための最適な選択です。
IronPDF では、その完全な機能の無料トライアルを提供しています。 また、C# .NET、Java、Python などの他の言語でも利用できます。 IronPDFのウェブサイトにアクセスして、詳細をご覧ください。








