Skip to main content
useImport is a React hook that handles CSV file imports. It parses CSV files and creates records in your data provider with support for batch processing and progress tracking.

Usage

import { useImport } from "@refinedev/core";

const PostList = () => {
  const { inputProps, isLoading } = useImport();

  return (
    <div>
      <input {...inputProps} disabled={isLoading} />
      {isLoading && <p>Importing...</p>}
    </div>
  );
};

Parameters

Return Values

inputProps
UseImportInputPropsType
Props to spread on an HTML file input element.
type
'file'
Input type.
accept
'.csv'
Accepted file types.
onChange
(event: React.ChangeEvent<HTMLInputElement>) => void
Change handler for the file input.
mutationResult
UseCreateReturnType | UseCreateManyReturnType
The mutation result from either useCreate or useCreateMany, depending on batchSize.
isLoading
boolean
Loading state indicator. true while parsing and importing data.
handleChange
HandleChangeType<TVariables, TData>
Manual handler function for file changes. Use this if you need custom file input handling.
(params: { file: Partial<File> }) => Promise<CreatedValuesType<TVariables, TData>[]>

Examples

Basic Import

import { useImport } from "@refinedev/core";

const PostImport = () => {
  const { inputProps, isLoading } = useImport();

  return (
    <div>
      <label>
        Import CSV:
        <input {...inputProps} disabled={isLoading} />
      </label>
      {isLoading && <p>Importing data...</p>}
    </div>
  );
};

Import with Data Mapping

import { useImport } from "@refinedev/core";

const UserImport = () => {
  const { inputProps } = useImport({
    mapData: (item) => ({
      name: item.Name,
      email: item.Email,
      role: item.Role || "user",
      active: item.Status === "Active",
    }),
  });

  return <input {...inputProps} />;
};

Import with Batch Size

import { useImport } from "@refinedev/core";

const BulkImport = () => {
  const { inputProps, isLoading } = useImport({
    batchSize: 50, // Process 50 records at a time
  });

  return (
    <div>
      <input {...inputProps} />
      {isLoading && <p>Processing in batches of 50...</p>}
    </div>
  );
};

Import with Progress Tracking

import { useImport } from "@refinedev/core";
import { useState } from "react";

const ImportWithProgress = () => {
  const [progress, setProgress] = useState(0);

  const { inputProps, isLoading } = useImport({
    onProgress: ({ totalAmount, processedAmount }) => {
      const percentage = (processedAmount / totalAmount) * 100;
      setProgress(percentage);
    },
  });

  return (
    <div>
      <input {...inputProps} />
      {isLoading && (
        <div>
          <p>Importing... {progress.toFixed(0)}%</p>
          <progress value={progress} max={100} />
        </div>
      )}
    </div>
  );
};

Import with Completion Handler

import { useImport, useNotification } from "@refinedev/core";

const ImportWithNotification = () => {
  const { open } = useNotification();

  const { inputProps } = useImport({
    onFinish: (results) => {
      const successCount = results.succeeded.length;
      const errorCount = results.errored.length;

      open?.({
        type: successCount > 0 ? "success" : "error",
        message: "Import Complete",
        description: `Successfully imported ${successCount} records. ${errorCount} errors.`,
      });
    },
  });

  return <input {...inputProps} />;
};

Custom File Input Button

import { useImport } from "@refinedev/core";
import { useRef } from "react";

const CustomImportButton = () => {
  const { inputProps, isLoading } = useImport();
  const fileInputRef = useRef<HTMLInputElement>(null);

  return (
    <div>
      <input
        {...inputProps}
        ref={fileInputRef}
        style={{ display: "none" }}
      />
      <button
        onClick={() => fileInputRef.current?.click()}
        disabled={isLoading}
      >
        {isLoading ? "Importing..." : "Import CSV"}
      </button>
    </div>
  );
};

Import with Custom CSV Parsing

import { useImport } from "@refinedev/core";

const CustomParseImport = () => {
  const { inputProps } = useImport({
    paparseOptions: {
      header: true,
      skipEmptyLines: true,
      delimiter: ";",
      encoding: "UTF-8",
    },
  });

  return <input {...inputProps} />;
};

Import with One-by-One Processing

import { useImport } from "@refinedev/core";

const SequentialImport = () => {
  const { inputProps, isLoading } = useImport({
    batchSize: 1, // Process one record at a time
  });

  return (
    <div>
      <input {...inputProps} />
      {isLoading && <p>Processing records sequentially...</p>}
    </div>
  );
};

Manual File Handling

import { useImport } from "@refinedev/core";

const ManualImport = () => {
  const { handleChange, isLoading } = useImport();

  const onFileSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      const results = await handleChange({ file });
      console.log("Import results:", results);
    }
  };

  return (
    <input
      type="file"
      accept=".csv"
      onChange={onFileSelect}
      disabled={isLoading}
    />
  );
};

Advanced Import with Error Handling

import { useImport, useNotification } from "@refinedev/core";
import { useState } from "react";

const AdvancedImport = () => {
  const { open } = useNotification();
  const [errors, setErrors] = useState([]);

  const { inputProps, isLoading } = useImport({
    batchSize: 100,
    mapData: (item) => ({
      title: item.Title,
      content: item.Content,
      status: item.Status || "draft",
    }),
    onFinish: (results) => {
      if (results.errored.length > 0) {
        setErrors(results.errored);
        open?.({
          type: "error",
          message: "Import Errors",
          description: `${results.errored.length} records failed to import.`,
        });
      } else {
        open?.({
          type: "success",
          message: "Import Successful",
          description: `${results.succeeded.length} records imported successfully.`,
        });
      }
    },
  });

  return (
    <div>
      <input {...inputProps} />
      {isLoading && <p>Importing...</p>}
      {errors.length > 0 && (
        <div>
          <h3>Import Errors:</h3>
          <ul>
            {errors.map((error, index) => (
              <li key={index}>{JSON.stringify(error.response)}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
};

CSV Format

The CSV file should have headers in the first row. Example:
Name,Email,Status
John Doe,[email protected],Active
Jane Smith,[email protected],Active
Bob Johnson,[email protected],Inactive

API Reference

Type definitions:
export type MapDataFn<TItem, TVariables> = (
  item: TItem,
  index?: number,
  items?: TItem[],
) => TVariables;

export type OnFinishParams<TVariables, TData> = {
  succeeded: ImportSuccessResult<TVariables, TData>[];
  errored: ImportErrorResult<TVariables>[];
};

export type OnProgressParams = {
  totalAmount: number;
  processedAmount: number;
};

export type UseImportInputPropsType = {
  type: "file";
  accept: string;
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
If batchSize is greater than 1, your data provider must implement the createMany method. If it’s set to 1, only the create method is required.
Use the mapData function to validate and transform CSV data before creating records. This is the perfect place to set default values, format dates, or validate required fields.
Large CSV files with small batch sizes may result in many API requests. Consider using larger batch sizes for better performance, but ensure your API can handle batch operations.

Build docs developers (and LLMs) love