Generating a PDF in React

Below is a minimal approach to generating PDFs in React applications: 1. Install the Dependencies npm install html2canvas jspdf 2. A Hidden PDF Component 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React from "react"; import { format } from "date-fns"; interface PDFReportProps { reportDate: Date; totalItems: number; } const PDFReport = React.forwardRef<HTMLDivElement, PDFReportProps>( (props, ref) => { const { reportDate, totalItems } = props; return ( <div ref={ref} style={{ position: "absolute", top: "-9999px", left: "-9999px", width: "800px", }} > <h1>Report - {format(reportDate, "MMMM yyyy")}</h1> {totalItems > 0 ? ( <p>Your main PDF content goes here.</p> ) : ( <p>No data available this month.</p> )} </div> ); } ); PDFReport.displayName = "PDFReport"; export default PDFReport; We use React.forwardRef so that this component’s DOM node is accessible elsewhere. Position it off-screen (top: -9999px) so it never appears in the rendered UI. 3. A Button & Utility to Generate the PDF 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import React from "react"; import html2canvas from "html2canvas"; import jsPDF from "jspdf"; interface PDFGeneratorProps { onGenerate: () => void; } const PDFGenerator: React.FC<PDFGeneratorProps> = ({ onGenerate }) => ( <button onClick={onGenerate}>Download PDF</button> ); export default PDFGenerator; export const generatePDF = async ( reportRef: React.RefObject<HTMLDivElement>, reportTitle: string ): Promise<void> => { if (!reportRef.current) return; try { const canvas = await html2canvas(reportRef.current, { scale: 2, useCORS: true, backgroundColor: "#ffffff", }); const imgData = canvas.toDataURL("image/png"); const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4", }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const imgWidth = canvas.width; const imgHeight = canvas.height; const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight); const imgPdfWidth = imgWidth * ratio; const imgPdfHeight = imgHeight * ratio; const xOffset = (pdfWidth - imgPdfWidth) / 2; pdf.addImage(imgData, "PNG", xOffset, 0, imgPdfWidth, imgPdfHeight); pdf.save(`${reportTitle}.pdf`); } catch (err) { console.error("Error generating PDF:", err); } }; Key Points: ...

March 31, 2025 · 3 min · 581 words · Me

Using Google Fonts with Tailwind

Contents I’ve been working on a handful of web apps made stylish by TailwindCSS but was a bit disappointed that I couldn’t easily figure out how to use a Google Font in place of the default font. This tutorial fixes that. Find your Font Go to Google Fonts and search for the font that fits your project. Once you’ve found it, click the “Get Font” button at the upper right of the screen: ...

March 31, 2025 · 2 min · 271 words · Me

Building a Multi-Label Classification Model with BERT

Steps to create a multi-label classification model using BERT and the Hugging Face Transformers library: Load the Data: Read the CSV file containing text snippets and their corresponding labels. Parse Labels: Convert the comma-separated labels into a list format. Prepare Labels: Use MultiLabelBinarizer to transform label lists into multi-hot vectors. Train/Test Split: Split the data into training and validation sets. Tokenize Text: Use a BERT tokenizer to preprocess the text snippets. Create Dataset: Define a custom Dataset class for the tokenized data and labels. Load Model: Initialize a BERT model for sequence classification with multi-label support. Training Arguments: Set up training parameters such as batch size, learning rate, and number of epochs. Train the Model: Use the Hugging Face Trainer to train the model. Save the Model: Save the trained model and tokenizer for future use. Example Code import pandas as pd import torch import torch.nn as nn from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments from sklearn.model_selection import train_test_split from sklearn.preprocessing import MultiLabelBinarizer # Load and preprocess data df = pd.read_csv("data/calls_with_context_ALL_2025-03-07T01-09-11-931Z_labeled.csv") df["labels"] = df["labels"].fillna("").apply(lambda x: [lbl.strip() for lbl in x.split(",") if lbl.strip() != ""]) all_labels = ["cancel appointment", "collect patient info", "collect medicaid info", "collect insurance info", "confirm appointment", "general question", "intro/outro", "question about patient's chart", "reschedule appointment", "running late", "schedule appointment", "taking a message"] mlb = MultiLabelBinarizer(classes=all_labels) label_matrix = mlb.fit_transform(df["labels"]) # Train/test split train_df, val_df, train_labels, val_labels = train_test_split(df, label_matrix, test_size=0.1, random_state=42) # Tokenize text tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") train_encodings = tokenizer(list(train_df["snippetText"]), truncation=True, padding=True, max_length=128) val_encodings = tokenizer(list(val_df["snippetText"]), truncation=True, padding=True, max_length=128) # Create dataset class IntentDataset(torch.utils.data.Dataset): def __init__(self, encodings, labels): self.encodings = encodings self.labels = labels def __getitem__(self, idx): item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()} item["labels"] = torch.tensor(self.labels[idx]).float() return item def __len__(self): return len(self.labels) train_dataset = IntentDataset(train_encodings, train_labels) val_dataset = IntentDataset(val_encodings, val_labels) # Load model and set training arguments model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=len(all_labels), problem_type="multi_label_classification") training_args = TrainingArguments(output_dir="./multi_intent_model", evaluation_strategy="epoch", per_device_train_batch_size=8, per_device_eval_batch_size=8, num_train_epochs=4, learning_rate=2e-5, weight_decay=0.01, logging_steps=50, load_best_model_at_end=True, save_strategy="epoch") # Train the model trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset, eval_dataset=val_dataset) trainer.train() # Save the model trainer.save_model("./multi_intent_model") tokenizer.save_pretrained("./multi_intent_model") print("Training complete!") Important Ensure you have the necessary libraries installed: transformers, torch, pandas, scikit-learn. ...

March 6, 2025 · 2 min · 350 words · Me

Quick How-To: Enabling and Disabling ESLint Rules for TypeScript

Configuring ESLint for TypeScript projects involves enabling and disabling specific rules to suit your project’s needs. Here’s a quick guide on how to manage these rules in your eslint.config.mjs file. Example Configuration import typescriptPlugin from "@typescript-eslint/eslint-plugin"; import typescriptParser from "@typescript-eslint/parser"; export default [ { files: ["**/*.{ts,tsx}"], languageOptions: { parser: typescriptParser, parserOptions: { ecmaVersion: "latest", sourceType: "module", project: "./tsconfig.json", }, }, plugins: { "@typescript-eslint": typescriptPlugin, }, rules: { ...typescriptPlugin.configs.recommended.rules, "no-console": "warn", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-ts-comment": "off", }, }, ]; Key Points Disabling Rules: Set the rule to "off". For example, to disable the @typescript-eslint/no-explicit-any rule: ...

March 6, 2025 · 1 min · 138 words · Me

Getting File Stats in Electron

You can get basic information about a file using the fs.stat method. This asynchronous function accepts a file path and returns an object that looks like this: { "dev": 16777234, "mode": 33188, "nlink": 1, "uid": 501, "gid": 20, "rdev": 0, "blksize": 4096, "ino": 806397637, "size": 311339, "blocks": 616, "atimeMs": 1740349370637.4573, "mtimeMs": 1740349369783.987, "ctimeMs": 1740349369783.987, "birthtimeMs": 1740349369783.5325, "atime": "2025-02-23T22:22:50.637Z", "mtime": "2025-02-23T22:22:49.784Z", "ctime": "2025-02-23T22:22:49.784Z", "birthtime": "2025-02-23T22:22:49.784Z" } Here’s how you could use it to check if the file is empty: const stats = await fs.stat(filePath); if(stats.size === 0){ // Do something }

February 23, 2025 · 1 min · 90 words · Me

Valuable Social Rewards

There are not many social media websites that reward you for objectively useful behavior. Twitter rewards yapping, Instagram rewards deceptive perspective, LinkedIn rewards slop. Stack Overflow, on the other hand, rewards knowledge that leads to problem solving. Despite the green badge impacting my life just as much as a red or blue badge, the green badge on the SO trophy raises my dopamine levels just a little higher. ...

February 17, 2025 · 1 min · 86 words · Me

Naming Things Better

There’s a section in the Electron docs on naming things that I thought was worth saving. If a module is a class or can be instantiated as one, name it using PascalCase If a module is a set of APIs or utils, name it using camelCase AirBnB also has a section on naming conventions which generally offers the same guidance. They go one step further and note that file names should exactly match the main export of that file: ...

February 16, 2025 · 1 min · 103 words · Me

Quote Steve Jobs Death

I watched the Steve Jobs biographical drama this weekend and was surprised for the fifth time that Jobs was not really a technical person. Compared to the layman, sure, but his entire career and the rise of Apple was due mostly to his obsession with craftsmanship. Remembering that you are going to die is the best way I know to avoid the trap of thinking you have something to lose. You are already naked. There is no reason not to follow your heart ...

February 14, 2025 · 1 min · 85 words · Me

VS Code SVG Extension

I spent 3-4 hours creating a Flutter app to edit and export SVGs only to find a VS Code extension that does both of these things in a few clicks. The extension is simply called SVG and it has almost 2 million downloads. The best part is that it allows you to edit SVGs directly in the editor and export them as PNGs. After installing the extension, right-click on one of your PNGs and select “Preview SVG” to start editing. This will open a new tab with the SVG preview and a toolbar at the top. In the toolbar, select the “Code interactive” button and click on any element in the SVG to start editing it. This will open a panel on the left with the SVG code. ...

February 11, 2025 · 1 min · 176 words · Me

Hugo Code Highlight

After reading about Expressive code highlighting in Astro, I was curious if it was possible to achieve the same in Hugo. This post is proof that it is, but it wasn’t as straightforward as I had hoped. Since I’m using the PaperMod theme, highlighting doesn’t work the same as it does in generic Hugo themes (PaperMod is doing some opininated things with the syntax highlighting). Nonetheless, all I had to do was add this CSS to the themes/papermod/assets/css/post-single.css file: ...

February 10, 2025 · 2 min · 262 words · Me