DEV Community

Shashidhar Naik
Shashidhar Naik

Posted on

From Label Design to Bulk Print: A Complete React Integration Guide (Part 2)

Part 1 covered the what and why. This post is the full how — the complete workflow from designing a label template to bulk-printing hundreds of records, with real code from the React demo.


The Workflow

Labels Tab   →  Design a template, save it with a title + entity type
Master Table →  Select that layout from a dropdown
Table Rows   →  Check the records you want to print
Export       →  PNG · PDF · ZPL
Enter fullscreen mode Exit fullscreen mode

The key idea: the template and the data are separate. One layout can generate labels for hundreds of records. And because each template is tagged to an entity (employee, machine, storage), the master table only shows layouts relevant to it — you can't accidentally print a machine tag on an employee badge.


Install

npm install qrlayout-core qrlayout-ui
Enter fullscreen mode Exit fullscreen mode

Step 1: Define Entity Schemas

Before the designer opens, you declare what fields each entity has. This drives the live preview inside the designer — every {{field}} placeholder the user drops on the canvas renders with real sample data instantly.

const SAMPLE_SCHEMAS = {
  storage: {
    label: "Storage Master",
    fields: [
      { name: "binCode",     label: "BIN Code" },
      { name: "storageType", label: "Storage Type" },
      { name: "aisle",       label: "Aisle" },
      { name: "rack",        label: "Rack Number" },
    ],
    sampleData: {
      binCode: "BIN-A1-R4", storageType: "Pallet Rack",
      aisle: "Aisle 01",    rack: "R-44"
    }
  },
  // employee and machine schemas follow the same shape
};
Enter fullscreen mode Exit fullscreen mode

The demo defines three schemas — employee, machine, and storage — all passed to the designer at once. The user picks which entity a layout targets while designing it.


Step 2: Mount the Designer in React

qrlayout-ui is framework-agnostic, so you mount it with a ref and initialize it in useEffect. Three things matter here:

// App.tsx
useEffect(() => {
  if (subView !== 'designer' || !containerRef.current) return;

  designerRef.current = new QRLayoutDesigner({
    element: containerRef.current,
    entitySchemas: SAMPLE_SCHEMAS,
    initialLayout: editingLayout || { ...DEFAULT_NEW_LAYOUT, id: crypto.randomUUID() },
    onSave: (layout) => {
      storage.addLabel(layout);        // save to localStorage
      setLabels(storage.getLabels());  // refresh the list
      setSubView('list');
    }
  });

  return () => {
    designerRef.current?.destroy();    // always clean up
    designerRef.current = null;
  };
}, [subView, editingLayout]);
Enter fullscreen mode Exit fullscreen mode

When the user clicks Save inside the designer, onSave fires with a complete StickerLayout JSON object — name, target entity, dimensions, and all placed elements. That JSON is the single artifact that flows through the entire rest of the system.

[Screenshot: designer canvas with a storage BIN label being designed]


Step 3: Save and List Templates

Layouts are stored in localStorage under a single key as a JSON array. The addLabel function upserts — it updates an existing layout if the id matches, or pushes a new one:

addLabel: (layout: StickerLayout): void => {
  const labels = storage.getLabels();
  const index  = labels.findIndex(l => l.id === layout.id);
  if (index >= 0) { labels[index] = layout; } else { labels.push(layout); }
  storage.saveLabels(labels);
},
Enter fullscreen mode Exit fullscreen mode

The Labels tab renders all saved templates in a table showing name, target entity, dimensions, and element count. From here users can create new templates or re-open existing ones in the designer.

[Screenshot: Labels tab listing the three default templates]


Step 4: The Master Table — Select, Pick Layout, Export

This is the core of the workflow. Here's BinMaster condensed to the parts that matter:

// Only load layouts relevant to this entity
const binLabels = storage.getLabels().filter(l => l.targetEntity === 'storage');

// Get selected rows and active layout
const getSelectedBins = () => bins.filter(b => selectedBinIds.includes(b.id));
const getActiveLayout = () => labels.find(l => l.id === selectedLayoutId);

// Three export handlers — all follow the same shape
const handleExportPDF = async () => {
  const layout = getActiveLayout();
  const selected = getSelectedBins();
  if (!layout) return;
  await exportToBatchPDF({ layout, items: selected, printer: printer.current, baseFilename: 'batch-bin-labels' });
};
Enter fullscreen mode Exit fullscreen mode

When no rows are checked, the page shows step-by-step instructions. Once rows are selected, the export toolbar appears:

[Screenshot: export toolbar showing PNG / PDF / ZPL buttons with 3 bins selected]

The same pattern is used identically in MachineMaster and EmployeeMaster — only the entity name and field names change.


Step 5: The Three Export Formats

// PNG — one file downloaded per item
printer.renderToDataURL(layout, item, { format: 'png' })

// PDF — all items in one document (separate optional import, keeps core bundle light)
import { exportToPDF } from 'qrlayout-core/pdf';
const pdf = await exportToPDF(layout, items);
pdf.save(`labels.pdf`);

// ZPL — array of ZPL strings, one per label, saved as .txt
const zplCommands = printer.exportToZPL(layout, items);
// In production: send each string to your Zebra printer over TCP port 9100
Enter fullscreen mode Exit fullscreen mode

[Screenshot: downloaded PDF opened showing storage BIN labels]

Try It — Default Data Is Pre-Loaded

The demo seeds data on first load so you can try the full workflow immediately:

  • 3 layouts — Professional ID Badge, Equipment Asset Tag, Storage Location Label
  • 3 employees — Arjun Mehta, Priya Sharma, Kiran Patel
  • 3 machines — CNC Router X1, Industrial 3D Printer, Hydraulic Press
  • 3 storage bins — BIN-A1-R1 (Pallet Rack), BIN-A1-R2 (Shelf), BIN-B2-R1 (Cold Storage)

Open the live demo → Storage → check all bins → click PDF. Done.


Taking It to Production

The demo runs entirely in the browser — localStorage, client-side rendering, file downloads. Every layer has a clean production equivalent:

Demo Production
localStorage (layouts) DB table with a layout_json column
localStorage (bins/machines) Your existing ERP or database tables
filter(l => l.targetEntity === 'storage') WHERE target_entity = 'storage'
Browser PDF download Server-side render → stream to client
.txt ZPL download TCP socket → Zebra printer (port 9100)

The qrlayout API itself doesn't change — qrlayout-core has zero browser dependencies, so exportToPDF and printer.exportToZPL work identically in Node.js. The only thing you swap is where data comes from and where output goes.


What's Next

The next post covers react-qr-label — a dedicated React package that wraps this pipeline into native hooks and components, eliminating the useRef/useEffect boilerplate entirely.

And if you're building a production system, here's the full recommended flow using React on the frontend and Node.js on the backend:

React (Frontend)
  └── qrlayout-ui  →  user designs label, onSave fires layout JSON
  └── POST /api/layouts  →  send layout JSON to backend

Node.js (Backend)
  └── Store layout JSON in DB (Postgres jsonb / MongoDB document)
  └── GET /api/layouts?entity=storage  →  serve filtered layouts to frontend
  └── POST /api/labels/print
        ├── fetch layout from DB
        ├── fetch selected records from your ERP / DB
        ├── qrlayout-core: exportToPDF(layout, items)  →  stream PDF to client
        └── qrlayout-core: printer.exportToZPL(layout, items)  →  TCP → Zebra printer

React (Frontend)
  └── Master table receives layouts from API
  └── User selects rows, clicks Print
  └── Receives PDF stream or triggers ZPL print job on the server
Enter fullscreen mode Exit fullscreen mode

qrlayout-core runs identically on both sides — zero browser dependencies, same API in Node.js and the browser. React handles design and selection; Node.js handles heavy rendering and direct printer communication.


Links

Top comments (1)

Collapse
 
arun_kumar_bc65fb5a99022d profile image
arun kumar

Very practical approach. The ability to design once and generate labels in bulk with dynamic data.