Why Dromo?

The CSV importer built for React

Add CSV and Excel import to your React app with one component. TypeScript types, hooks-friendly API, AI column mapping, inline validation. Free dev mode.

View docs
import DromoUploader from "dromo-uploader-react";

<DromoUploader
  licenseKey="YOUR_FRONTEND_LICENSE_KEY"
  fields={[{ label: "Email", key: "email", type: "email" }]}
  onResults={(response, metadata) => console.log(response, metadata)}
>
  Launch Dromo Importer
</DromoUploader>

Add a CSV importer to React in 10 minutes

The React package wraps the importer in a single component. Install it, describe the columns you expect, render the component, and handle the array of clean rows it hands back.

npm install dromo-uploader-react

Start with the schema. Each field has a key your backend understands, a human label the user sees during column matching, and a list of validators. Keep this in its own module so the same definition can be reused for a headless import later.

// src/import/contactFields.ts
import type { IDeveloperField } from "dromo-uploader-react";

export const contactFields: IDeveloperField[] = [
  {
    label: "Email",
    key: "email",
    type: "email",
    validators: [{ validate: "required" }, { validate: "unique" }],
  },
  { label: "Full name", key: "full_name", type: "string", validators: [{ validate: "required" }] },
  { label: "Phone", key: "phone", type: "string" },
  {
    label: "Plan",
    key: "plan",
    type: "select",
    selectOptions: [
      { label: "Starter", value: "starter" },
      { label: "Team", value: "team" },
      { label: "Enterprise", value: "enterprise" },
    ],
  },
];

Now render the importer. The component renders a trigger button; the modal, upload step, matching step, and review grid are all managed internally. Your only job is the onResults callback.

// src/import/ImportContactsButton.tsx
import { useCallback } from "react";
import DromoUploader from "dromo-uploader-react";
import { contactFields } from "./contactFields";

interface Props {
  onImported: (rows: Record<string, unknown>[]) => void;
}

export function ImportContactsButton({ onImported }: Props) {
  const handleResults = useCallback(
    (data: Record<string, unknown>[]) => onImported(data),
    [onImported]
  );

  return (
    <DromoUploader
      licenseKey={import.meta.env.VITE_DROMO_KEY}
      fields={contactFields}
      settings={{ importIdentifier: "contacts", developmentMode: true }}
      user={{ id: "user_42" }}
      onResults={handleResults}
    >
      Import contacts
    </DromoUploader>
  );
}

The rows that reach onImported have already passed every validator, so the parent component can POST them straight to your API without a second round of checks. In practice that is a fetch to a bulk endpoint followed by a query invalidation or a state update, and the feature is done.

How it fits the React model

The importer is a leaf in your component tree, not a page you route to. Configuration flows in through props, results flow out through a callback, and nothing about it requires global state. If you already keep the current user or tenant in a context provider, read from that provider in the parent and pass the values down; the component does not need to know about your context.

Because the fields array is plain data, you can derive it from state. A workspace with custom properties can build the schema at render time, and a feature flag can toggle a validator on or off. The uploader treats a changed fields prop as a new configuration the next time it opens.

TypeScript types ship with the package. IDeveloperField describes the schema, and the settings object is typed so that a misspelled option fails at compile time rather than at demo time. If your codebase is strict about unknown, narrow the result rows with a runtime schema library of your choice before they touch the database.

React gotchas Dromo handles for you

Unstable callback references. A file parser wired up in useEffect re-subscribes every time the parent re-renders, because the callback identity changed. Teams end up with duplicate parse events or stale closures reading old state. The uploader holds its own lifecycle, so the callback you pass is invoked once per completed import regardless of how many times the parent renders.

StrictMode double invocation. In development, React mounts, unmounts, and remounts components to surface impure effects. A hand-rolled importer that kicks off parsing on mount will parse the file twice and, on a bad day, submit it twice. Dromo does nothing on mount; the import begins only when the user acts.

The wizard state machine. Upload, header detection, column matching, error review, and submit are five states with transitions between them, plus a "go back and fix the mapping" edge that everyone forgets. Modeling that in useReducer is a week of work before any validation exists. The component owns the machine, and you receive a single terminal event.

Large files on the main thread. Parsing a 200 MB export in the render thread freezes the tab, and holding the whole thing in component state can exhaust memory. Dromo streams the file through the review grid without pinning it in your state tree, and users can download a partially fixed workbook to finish offline and resume later.

Uncontrolled file inputs. <input type="file"> cannot be controlled, so resetting it after a failed upload means juggling a key or a ref. There is no file input in your code at all.

Dromo vs building it with PapaParse

Build it yourself Dromo
Column mapping Write the header-matching UI, fuzzy matching, and per-user memory of past mappings AI suggestions match headers to your schema and remember the choice between imports
Validation Hand-write checks per field, plus a way to display them Types, regex, ranges, uniqueness, allowed values, and custom validators that call your API
Error correction UI Build a spreadsheet grid with inline editing, or bounce users back to Excel Errors highlighted inline in a review grid the user edits in place
Large files Web Workers, chunked parsing, and careful memory management Handled inside the widget; users can also fix offline and re-upload to resume
Internationalization Translate every label and message yourself 30+ languages built in
Ongoing maintenance Every new customer format is a ticket for your team Configuration changes, often made by a PM in Schema Studio

If your input is a well-formed CSV produced by a system you control, PapaParse and a hundred lines of code is the right call, and you should not pay for an importer. The line is crossed when the files come from customers: mixed headers, merged cells, Excel exports, and rows that are wrong in ways only the uploader can fix. At that point the review UI, not the parser, is the expensive part.

Ship it to your users

White labeling is included on every plan at no extra cost. Colors, fonts, and every string in the flow can be changed so the modal reads as part of your product; there is no Dromo logo to hide and no upgrade required to remove one.

Translations for more than thirty languages are built in. A customer in Brazil sees the matching step in Portuguese without your team maintaining a locale file, and the copy overrides you make apply per language.

For accounts that will not let a file leave their machine, Private Mode runs the entire import inside the browser with zero retention on Dromo's side. Results still arrive through the same onResults callback, so the React integration does not change; only a settings flag does.

What Our Customers Say

G2 High Performer Award 2025 badge for Dromo
4.9 out of 5 stars
5 out of 5 stars
Ryan Chan logo
Ryan Chan
CEO at Upkeep.com
E&Y Entrepreneur of the Year finalist
"We have now rolled out Dromo across our entire customer onboarding experience, and it has made our lives so much easier. Most importantly, our customers love the new experience!"

FAQ

Frequently asked questions

  • Yes. The React package ships with type definitions for the component props, schema fields, and the onResults payload.
  • Yes. Open and close it from state, pass your schema as props, and handle results in the onResults callback like any other component.
  • Yes. It is a client-side component and works in any React setup. For frameworks with server rendering, load it on the client only.
  • Yes. Free dev mode has no credit card requirement and a 100-row cap, so you can wire it into your app before buying.

Ready to Ship a Better Data Import Experience?

AI-powered mapping. Real-time validation. SOC 2 certified. Deploys in minutes.