Express CSV Logo

Delivery

  1. The user completes the import flow in the importer.
  2. ExpressCSV validates the rows in the browser and groups them into chunks.
  3. onData receives each chunk in your app.
  4. Your app sends that chunk to your backend and calls next() to move to the next chunk.
  5. onComplete({ sessionId }) finalizes the staged import, while onCancel({ sessionId }) and onError(error, { sessionId }) allow you to handle aborted imports and clean up any partial work you may have started.

Basic Example

import { useExpressCSV, x } from "@expresscsv/react";

const schema = x.row({
  name: x.string().label("Full Name"),
  email: x.string().email().label("Email Address"),
});

export function ImportUsersButton() {
  const { open } = useExpressCSV({
    schema,
    getSessionToken: async () => fetchSessionToken(),
    importIdentifier: "user-import",
  });

  return (
    <button
      type="button"
      onClick={() =>
        open({
          chunkSize: 500,
          onData: async (chunk, next) => {
            const response = await fetch("/api/import-users/chunks", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                sessionId: chunk.sessionId,
                chunkIdempotencyKey: chunk.chunkIdempotencyKey,
                records: chunk.records,
                currentChunkIndex: chunk.currentChunkIndex,
                totalChunks: chunk.totalChunks,
                totalRecords: chunk.totalRecords,
              }),
            });

            if (!response.ok) {
              throw new Error("Backend rejected this import chunk");
            }

            next();
          },
          onComplete: async ({ sessionId }) => {
            await fetch("/api/import-users/complete", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ sessionId }),
            });
          },
          onCancel: async ({ sessionId }) => {
            await fetch("/api/import-users/abort", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ sessionId }),
            });
          },
          onError: async (error, { sessionId }) => {
            console.error("Delivery error", error);

            await fetch("/api/import-users/abort", {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify({ sessionId }),
            });
          },
        })
      }
    >
      Import users
    </button>
  );
}

Backpressure

Chunks are delivered sequentially. ExpressCSV will not send the next chunk until your onData handler calls next(), which lets your app wait while your backend writes the current chunk. Call next() only after the chunk is durably accepted by your backend.

Errors

If onData throws or returns a rejected promise before calling next(), delivery stops and onError(error, { sessionId }) runs. Use onError to report the failure and clean up any partial work you started for that import session.