Passer au contenu du pied de page
OUTILS PDF

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 diverses bibliothèques pour générer des PDFs et apprendrons comment utiliser la célèbre bibliothèque jsPDF pour créer des fichiers PDF directement depuis 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.

Elle vous permet de contrôler le formatage et la mise en page de vos PDFs, du changement de la taille et de la couleur de la police à l'ajustement de l'orientation et de la taille de la page. jsPDF est une solution robuste qui fonctionne dans les environnements navigateur et serveur, ce qui en fait un excellent choix pour un large éventail d'applications JavaScript.

pdfmake

pdfmake se distingue comme une solution d'impression PDF client/serveur en pur JavaScript. 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 objet JavaScript simple, puis le transformer en un document PDF valide.

React-PDF

React-PDF est une bibliothèque unique qui offre des fonctionnalités puissantes pour créer des 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 proposent 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 explorerons avec jsPDF vous donneront une base solide pour générer des PDFs, et vous pourrez plus facilement adopter 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
SHELL

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
SHELL

Comment créer un fichier PDF dans React : Figure 1 - Une capture d'écran du terminal montrant la commande ci-dessus en cours.

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
SHELL

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 à l'aide de 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
SHELL

É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. Cela inclut divers composants de la bibliothèque Material-UI, la bibliothèque jsPDF pour générer des PDFs, et les utilitaires de stylisation.

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";
JAVASCRIPT

Création de composants stylisés

Pour ajouter un comportement cohérent entre navigateurs à 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,
  },
}));
JAVASCRIPT

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 dans 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);
JAVASCRIPT

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 article lorsqu'un utilisateur les modifie. La fonction addItem ajoute un nouvel article à la liste. La fonction deleteItem supprime un article 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);
};
JAVASCRIPT

Génération de la facture

Voici le code de la 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);
};
JAVASCRIPT

Dans la fonction generateInvoice, nous commençons par effectuer une validation sur les champs d'entrée pour s'assurer que le nom, 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 à true et retournons 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 en points.

Nous commençons ensuite à ajouter le contenu à notre document PDF. Nous définissons la taille de la police en utilisant 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 à l'aide de doc.line pour séparer les articles de l'en-tête. Ensuite, nous parcourons chaque article dans le tableau items et calculons le prix total pour chaque article en multipliant la quantité par le prix. Nous mettons à jour la variable total avec la somme de tous les totaux d'articles.

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 article. 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 d'affichage. 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.

Elle 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 les 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>
);
JAVASCRIPT

Code complet de App.js et App.css

Voici le code complet de 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;
JAVASCRIPT

Voici le code de 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
SHELL

Cela lancera le serveur de développement, et vous pourrez voir l'application dans votre navigateur à l'adresse http://localhost:3000.

Comment créer un fichier PDF dans React - Figure 2 : L'application de facture complétée avec les champs par défaut, non remplis.

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.

Comment créer un fichier PDF dans React - Figure 3 : L'application avec trois articles remplis, avec des articles, quantités et prix variables.

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

Comment créer un fichier PDF dans React - Figure 4 : Le PDF 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.

Comment créer un fichier PDF dans React - Figure 5 : Un message d'erreur est affiché car tous les champs n'ont pas été remplis.

IronPDF - La bibliothèque PDF pour Node.js

IronPDF for 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.

Here is an example of generating a PDF document from an HTML File, HTML String, and 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");
})();
JAVASCRIPT

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 forces et fonctionnalités 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. It is also available for other languages like C# .NET, Java, and Python. Visitez le site web IronPDF pour plus de détails.

Darrius Serrant
Ingénieur logiciel Full Stack (WebOps)

Darrius Serrant est titulaire d'un baccalauréat en informatique de l'université de Miami et travaille comme ingénieur marketing WebOps Full Stack chez Iron Software. Attiré par le codage dès son plus jeune âge, il a vu l'informatique comme à la fois mystérieuse et accessible, en faisant le ...

Lire la suite