Comment créer un fichier PDF en React
Bienvenue dans le tutoriel sur la création de documents PDF à partir d'une application React ! Dans ce tutoriel, nous explorerons différentes bibliothèques pour générer des PDF et apprendrons comment utiliser la populaire bibliothèque jsPDF pour créer des fichiers PDF directement à partir de vos composants React. Alors, plongeons et commençons !
Le PDF (Portable Document Format) est un format de fichier largement utilisé pour partager et imprimer des documents tout en préservant leur mise en page et leur formatage. En tant que développeur web React, vous pourriez rencontrer des scénarios où vous devez générer des documents PDF, tels que des factures, des rapports ou des contrats de vente, directement depuis votre application React.
Choisir une bibliothèque PDF pour React
Créer des documents PDF dans une application React peut être une tâche décourageante, surtout si vous êtes novice dans ce domaine. Heureusement, nous avons à notre disposition plusieurs bibliothèques tierces qui simplifient considérablement ce processus. Chaque bibliothèque a ses propres caractéristiques uniques et utilités, répondant à différents cas d'utilisation. Explorons ces bibliothèques un peu plus en détail.
jsPDF
jsPDF est une bibliothèque très populaire parmi les développeurs pour générer des fichiers PDF à partir de JavaScript. L'un de ses principaux arguments de vente est sa simplicité. Sa syntaxe et son utilisation sont assez simples, vous permettant de transformer votre contenu HTML en un fichier PDF en un rien de temps.
Il vous permet de contrôler la mise en forme et la disposition de vos PDF, de la modification de la taille et de la couleur de la police à l'ajustement de l'orientation et du format de la page. jsPDF est une solution robuste fonctionnant aussi bien dans un navigateur que sur un serveur, ce qui en fait un excellent choix pour une large gamme d'applications JavaScript .
pdfmake
pdfmake se distingue comme une solution d'impression PDF côté client/serveur en JavaScript pur. Cette bibliothèque est un excellent choix pour créer des PDFs plus complexes, grâce à son API complète et ses options de mise en page flexibles. Avec pdfmake, vous pouvez définir le contenu et la structure de votre document à l'aide d'un simple objet JavaScript , puis le transformer en un document PDF valide.
React-PDF
React-PDF est une bibliothèque unique qui offre des fonctionnalités puissantes pour la création de fichiers PDF à l'aide de composants React. Au lieu de rédiger manuellement la structure de votre document dans un objet JavaScript, vous pouvez créer votre PDF comme vous le feriez pour construire une application React typique - en utilisant des composants réutilisables et des props. Le site web d'IronPDF a un tutoriel sur la création de PDFs en utilisant la bibliothèque React-PDF.
Pourquoi choisir jsPDF ?
Bien que les trois bibliothèques fournissent des outils puissants pour générer des documents PDF dans React, nous utiliserons jsPDF dans ce tutoriel en raison de sa simplicité, de sa flexibilité et de sa large adoption dans la communauté. Elle offre une barrière d'entrée moindre pour les débutants, et son ensemble de fonctionnalités robustes en fait un choix approprié pour de nombreux cas d'utilisation. Les principes que nous allons explorer avec jsPDF vous donneront une base solide pour générer des PDF, et vous pourrez vous familiariser plus facilement avec d'autres bibliothèques si votre projet l'exige.
Prérequis
Avant de plonger dans ce tutoriel, il est essentiel de s'assurer que vous êtes adéquatement équipé des outils et connaissances nécessaires pour suivre correctement. Les prérequis pour ce tutoriel sont les suivants :
Compréhension de base de React
Tout d'abord, vous devriez avoir une compréhension de base de React, une bibliothèque JavaScript populaire pour la construction d'interfaces utilisateur, particulièrement les applications monopages. Vous devriez être familier avec des concepts comme JSX (JavaScript XML), les composants, l'état et les props dans React.
Environnement de développement
Vous devriez également avoir un environnement de développement configuré sur votre ordinateur pour construire des applications React. Cela inclut un éditeur de texte ou un environnement de développement intégré (IDE). Les éditeurs de texte tels que Visual Studio Code, Atom ou Sublime Text sont de bonnes options.
Node.js et npm
Pour gérer notre projet et ses dépendances, nous utiliserons Node.js et npm (Node Package Manager). Assurez-vous que vous avez Node.js installé sur votre ordinateur. Node.js est un moteur JavaScript qui nous permet d'exécuter JavaScript sur nos serveurs. Il est livré avec npm installé, afin que vous puissiez gérer les bibliothèques requises pour votre projet.
Vous pouvez vérifier si Node.js et npm sont installés en exécutant les commandes suivantes dans le terminal :
node -v
npm -v
node -v
npm -v
Ces commandes afficheront la version de Node.js et de npm installée sur votre système, respectivement. Si vous ne les avez pas installés ou si vos versions sont obsolètes, vous devriez télécharger et installer la dernière version Long Term Support (LTS) de Node.js depuis leur page de téléchargement officielle.
Étape 1 : Configuration du projet
Commençons par configurer notre projet React. Ouvrez votre terminal et naviguez vers le répertoire souhaité où vous souhaitez créer votre projet. Exécutez la commande suivante pour créer une nouvelle application React :
npx create-react-app pdf-from-react
npx create-react-app pdf-from-react

Cette commande créera un nouveau répertoire appelé pdf-from-react avec une structure de projet React de base.
Ensuite, naviguez dans le répertoire du projet :
cd pdf-from-react
cd pdf-from-react
Maintenant, nous pouvons ouvrir le projet dans notre éditeur de code et procéder à l'implémentation.
Étape 2 : Ajout des dépendances requises
Tout d'abord, nous devons installer les packages nécessaires. Installez react, react-dom, @mui/material et jspdf en utilisant la commande terminal suivante.
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
npm install jspdf @mui/material @emotion/react @emotion/styled @mui/icons-material
Étape 3 : Construction de la fonctionnalité de génération de PDF
Importer les bibliothèques
Nous commençons par importer les dépendances nécessaires pour notre application. Ceux-ci comprennent divers composants de la bibliothèque Material-UI, la bibliothèque jsPDF pour la génération de PDF et des utilitaires de style.
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";
Création de composants stylisés
Pour ajouter un comportement multi-navigateurs cohérent à notre application, nous avons utilisé l'utilitaire styled de la bibliothèque MUI pour créer StyledTableCell et 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,
},
}));
Création du composant App
Le composant principal de notre application est le composant App. Nous avons quatre variables d'état : customerName et customerAddress pour suivre les données du client, items pour suivre la liste des articles de la facture et error pour afficher un message d'erreur si nécessaire.
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);
Gestion de l'entrée utilisateur
Dans ce bloc de code, nous avons défini des fonctions pour gérer les interactions utilisateur : changer les détails de l'article, ajouter un nouvel article, et supprimer un article. La fonction handleItemChange met à jour les propriétés d'un élément lorsqu'un utilisateur les modifie. La fonction addItem ajoute un nouvel élément à la liste. La fonction deleteItem supprime un élément de la liste.
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);
};
Génération de la facture
Voici le code de fonction 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);
};
Dans la fonction generateInvoice, nous effectuons d'abord une validation des champs d'entrée pour nous assurer que le nom du client, l'adresse du client et les détails de l'article sont remplis. Si l'un de ces champs est vide, nous définissons l'état error sur true et retournons plus tôt.
Ensuite, nous créons une nouvelle instance de jsPDF en appelant new jsPDF("p", "pt"). Le premier argument "p" spécifie l'orientation de la page comme portrait, et le deuxième argument "pt"" spécifie l'unité de mesure comme points.
Nous commençons ensuite à ajouter le contenu à notre document PDF. Nous définissons la taille de la police à l'aide de doc.setFontSize et utilisons la méthode doc.text pour ajouter du texte à des coordonnées spécifiques sur la page. Nous ajoutons l'en-tête de la facture, y compris le titre, le numéro de la facture, la date, le nom du client et l'adresse du client.
Après l'en-tête, nous ajoutons la section " Articles " en définissant la taille de la police et en ajoutant une ligne utilisant doc.line pour séparer les articles de l'en-tête. Ensuite, nous parcourons chaque élément du tableau items et calculons le prix total de chaque élément en multipliant la quantité par le prix. Nous mettons à jour la variable total avec la somme des prix totaux de tous les éléments.
Pour chaque article, nous utilisons doc.text pour ajouter le nom de l'article, la quantité, le prix et le total de l'article au document PDF. Nous incrémentons la variable yOffset pour passer à la ligne suivante pour chaque élément. Enfin, nous ajoutons une ligne pour séparer les articles du total et utilisons doc.text pour ajouter le montant total en bas à droite du document.
Une fois le contenu ajouté, nous utilisons doc.save("invoice.pdf") pour enregistrer le PDF généré sous le nom " invoice.pdf " sur l'ordinateur de l'utilisateur. Enfin, nous réinitialisons l'état error à false pour effacer toutes les erreurs de validation précédentes.
Étape 4 : Affichage de l'interface utilisateur
L'instruction return contient le code JSX qui gère le processus de rendu. Elle inclut des champs d'entrée pour le nom et l'adresse du client, un tableau pour saisir les détails des articles, des boutons pour ajouter des articles et générer la facture, et un snackbar d'erreur pour afficher les erreurs de validation.
Il utilise des composants de la bibliothèque Material-UI, tels que Button, TextField, Box, Container, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton, Snackbar, et Alert, pour créer des composants de base. Ces composants sont utilisés pour créer les champs de formulaire, les tableaux, les boutons, et les notifications d'erreur.
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>
);
Code complet de App.js et App.css
Voici le code complet App.js que vous pouvez copier et coller dans votre projet :
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;
Voici le code 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);
}
}
Étape 5 : Tester l'application
Pour tester la fonctionnalité de génération de PDF, exécutez la commande suivante dans le terminal :
npm start
npm start
Cela démarrera le serveur de développement, et vous pourrez visualiser l'application dans votre navigateur à l'adresse http://localhost:3000 .

Remplissez le nom du client, l'adresse, et les détails des articles dans les champs d'entrée et cliquez sur le bouton "Generate Invoice". Le fichier PDF sera téléchargé sur votre ordinateur, et vous pourrez l'ouvrir pour voir la vue pleine page de la facture générée.

Lorsque vous cliquez sur le bouton "Generate Invoice", le fichier PDF sera généré.

Si vous essayez de générer un PDF avec un champ vide, un message d'erreur s'affichera en haut à droite.

IronPDF - La bibliothèque PDF pour Node.js
IronPDF pour Node.js est une bibliothèque PDF complète pour Node.js qui excelle en précision, facilité d'utilisation et rapidité. Il offre une vaste gamme de fonctionnalités pour générer, éditer et formater des PDF directement à partir de HTML, d'URLs et d'images dans React. Avec la prise en charge de différentes plateformes, y compris Windows, MacOS, Linux, Docker, et les plateformes cloud comme Azure et AWS, IronPDF assure une compatibilité multiplateforme. Son API conviviale permet aux développeurs d'intégrer rapidement la génération et la manipulation de PDF dans leurs projets Node.js.
Les caractéristiques notables d'IronPDF Node.js incluent : rendu précis au pixel près, nombreuses options de formatage, et fonctionnalités d'édition avancées comme la fusion et la séparation de PDFs, l'ajout d'annotations, et la création de formulaires PDF.
Voici un exemple de génération d'un document PDF à partir d'un fichier HTML, une chaîne HTML, et une URL :
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");
})();
Pour plus d'exemples de code sur les tâches liées aux PDF, veuillez visiter cette page d'exemples de code IronPDF.
Conclusion
En conclusion, créer des PDFs dans une application React ne doit pas être intimidant. Avec les bons outils et une compréhension claire, vous pouvez générer facilement des documents PDF bien structurés et esthétiques. Nous avons exploré diverses bibliothèques telles que jsPDF, pdfmake et React-PDF, chacune avec ses propres atouts et caractéristiques uniques.
Avec le processus d'intégration direct d'IronPDF pour des frameworks et bibliothèques en JavaScript, une documentation excellente, et un support technique réactif, les développeurs peuvent commencer rapidement, ce qui en fait un choix de premier ordre pour générer des PDFs de qualité professionnelle dans des applications Node.js.
IronPDF offre une version d'essai gratuite de ses fonctionnalités complètes. Elle est également disponible pour d'autres langages comme C# .NET, Java, et Python. Visitez le site web d'IronPDF pour plus de détails.



