0.2.0 • Published 3 months ago

react-importer v0.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

react-importer

A modern CSV importer written in Preact

NPM JavaScript Style Guide Build Status License

Contents

Why?

Every single project I've worked on needed some hacked together CSV importer. React Importer tries to fix this by implementing a modern CSV import flow for React projects!

Demo

Visit here to see what React Importer looks like in action!

Installation

npm install --save react-importer

React and Vite

  • Since this project uses Preact, it is necesary to add the following code to your vite.config.ts to ensure compatibility if your project relies on React
  • Detailed documenation on aliasing React to Preact can be found here
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      react: 'preact/compat',
      'react-dom': 'preact/compat',
    },
  },
});

Usage

import Importer from 'react-importer/peer';
import 'react-importer/peer/index.css';

<Importer
  sheets={[
    {
      id: 'employees',
      label: 'Employees',
      columns: [
        {
          label: 'Name',
          id: 'name',
          type: 'string',
          validators: [{ validate: 'required' }],
        },
        {
          label: 'Email',
          id: 'email',
          type: 'string',
          validators: [
            { validate: 'required' },
            { validate: 'unique', error: 'This email is not unique' },
            {
              validate: 'regex_matches',
              regex:
                /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
              error: 'This email is not valid',
            },
          ],
        },
        {
          label: 'Phone Number',
          id: 'phone_number',
          type: 'string',
          validators: [{ validate: 'required' }],
        },
        { label: 'City', id: 'city', type: 'string' },
        {
          label: 'State',
          id: 'state',
          type: 'string',
          isReadOnly: true,
          transformers: [{ transformer: 'state_code' }],
        },
      ],
    },
    {
      id: 'companies',
      label: 'Companies',
      columns: [
        {
          label: 'Company',
          id: 'company',
          type: 'string',
          validators: [{ validate: 'required' }],
        },
        {
          label: 'Industry',
          id: 'industry',
          type: 'enum',
          typeArguments: {
            values: [
              { label: 'Tech', value: 'tech' },
              { label: 'Finance', value: 'finance' },
            ],
          },
          validators: [{ validate: 'required' }],
        },
        {
          label: 'Name',
          id: 'name',
          type: 'reference',
          typeArguments: {
            sheetId: 'employees',
            sheetColumnId: 'name',
          },
          validators: [{ validate: 'required' }],
        },
      ],
    },
  ]}
  onComplete={(data) => {
    console.log(data);
  }}
/>;

Documentation

Props

Prop NameDefault ValueAvailable ValuesRequiredDescription
sheets-SheetDefinition[]YesArray of sheet definitions that describe the structure of data to be imported
onComplete-(data: ImporterState, onProgress: (progress: number) => void) => Promise<void>YesCallback function called when the import process is completed. Receives the final data and a progress callback
theme'default''default' | 'theme-1' | 'theme-2'NoVisual theme variant for the importer component
onDataColumnsMapped-(data: SheetState) => Promise<SheetState> \| SheetStateNoCallback function called after columns are mapped to sheet definitions by the user
allowManualDataEntryfalsebooleanNoWhether to allow users to manually enter data during the preview phase
localeen'en' | 'fr'NoLocale string for internationalization
preventUploadOnValidationErrorsfalseboolean | (errors: ImporterValidationError[]) => booleanNoControls whether to prevent upload when validation errors occur. Can be a boolean or a function that returns a boolean based on the errors
SheetDefinition Props
Prop NameDefault ValueAvailable ValuesRequiredDescription
id-stringYesUnique identifier for the sheet
label-stringYesDisplay name for the sheet
columns-SheetColumnDefinition[]YesArray of column definitions that describe the structure of the sheet data
Column Types
TypeDescriptionAdditional Props
stringBasic string column-
numberNumeric column-
referenceReferences data from another sheet. Referenced columns are automatically filled and are not available for the user to select in the mapping phasetypeArguments: { sheetId: string, sheetColumnId: string }
enumColumn with predefined valuestypeArguments: { values: SelectOption<string>[] }
Common Column Props

All column types share these base properties:

  • id: string - Unique identifier for the column
  • type: ColumnType - Type of the column (described above)
  • label: string - Display name for the column
  • suggestedMappingKeywords?: string[] - Keywords to help with automatic column mapping
  • isReadOnly?: boolean - Whether the column can be edited
  • validators?: ImporterValidatorDefinition[] - Array of validation rules
  • transformers?: ImporterTransformerDefinition[] - Array of data transformation rules
ImporterState
Prop NameTypeRequiredDescription
sheetDefinitionsSheetDefinition[]YesArray of sheet definitions that define the structure of the import
currentSheetIdstringYesID of the currently selected sheet
modeImporterModeYesCurrent state of the importer ('upload' | 'mapping' | 'preview' | 'submit' | 'completed' | 'failed')
validationErrorsImporterValidationError[]YesArray of validation errors found in the data
sheetDataSheetState[]YesArray containing the actual data for each sheet
parsedFileParsedFileNoContains the parsed CSV file data if a file has been uploaded
rowFileFileNoThe original uploaded file object
columnMappingsColumnMapping[]NoArray of mappings between CSV columns and sheet columns
importProgressnumberYesProgress of the import operation (0-100)

Example:

{
  sheetDefinitions: [...],
  currentSheetId: "employees",
  mode: "preview",
  validationErrors: [],
  sheetData: [...],
  columnMappings: [
    { csvColumnName: "Name", sheetId: "employees", sheetColumnId: "name" }
  ],
  importProgress: 0
}
SheetState Props
Prop NameDefault ValueAvailable ValuesRequiredDescription
sheetId-stringYesUnique identifier matching the corresponding SheetDefinition
rows-SheetRow[]YesArray of data rows, where each row is a record of column IDs to their values
Validators
TypeDescriptionAdditional PropsExample
requiredEnsures the field is not emptyerror?: string{ validate: 'required', error: 'This field is required' }
uniqueEnsures the value is unique across all rowserror?: string{ validate: 'unique', error: 'This value must be unique' }
regex_matchesValidates against a regular expressionregex: string \| RegExp, error?: string{ validate: 'regex_matches', regex: /^[A-Z]+$/, error: 'Must be uppercase' }
includesChecks if value is in a predefined listvalues: ImporterOutputFieldType[], error?: string{ validate: 'includes', values: ['A', 'B', 'C'] }
multi_includesChecks if all values in a delimited string are in a predefined listvalues: ImporterOutputFieldType[], delimiter?: string \| RegExp, error?: string{ validate: 'multi_includes', values: ['A', 'B'], delimiter: ',' }
is_integerValidates that the value is an integererror?: string{ validate: 'is_integer' }
phone_numberValidates phone number formaterror?: string{ validate: 'phone_number' }
emailValidates email formaterror?: string{ validate: 'email' }
postal_codeValidates postal code formaterror?: string{ validate: 'postal_code' }
customCustom validation function. Returning string means the validation failed and the string returned is the message. Returning null/undefined means that validation passedkey: string, validateFn: (fieldValue: ImporterOutputFieldType, row: SheetRow) => ImporterValidatorOutput{ validate: 'custom', key: 'myValidator', validateFn: (value) => value.length > 5 ? null : 'Too short' }
Transformers
TypeDescriptionExample
phone_numberFormats phone numbers to a standard format{ transformer: 'phone_number' }
postal_codeFormats postal codes to a standard format{ transformer: 'postal_code' }
state_codeFormats state codes to a standard format{ transformer: 'state_code' }
stripRemoves leading and trailing whitespace{ transformer: 'strip' }
customCustom transformation function{ transformer: 'custom', key: 'myTransformer', transformFn: (value) => value.toUpperCase() }
ImporterValidationError
Prop NameTypeRequiredDescription
sheetIdstringYesID of the sheet where the validation error occurred
rowIndexnumberYesIndex of the row where the validation error occurred (0-based)
columnIdstringYesID of the column where the validation error occurred
messagestringYesError message describing what went wrong

Example:

{
  sheetId: "employees",
  rowIndex: 2,
  columnId: "email",
  message: "Invalid email format"
}

Peer vs Bundled

This package ships with 2 versions you can use:

  • /peer - this version is meant to be used with React/Preact - it defines preact as peer dependency
  • /bundled - this version is meant to be used without React/Preact - it ships with preact bundled. The user should then use renderImporter function, exported as named export

Customization

Height and Width

You can customize the dimensions by setting height, max-height, width, or max-width. To ensure proper scaling and responsiveness, it's important to include flex.

<div style={{ display: 'flex', height: '80vh' }}>
  <Importer .../>
</div>
Theme Styles

You can further customize theme styles by overriding the following CSS variables in your index.css file:

:root {
  --csv-importer-color-primary: #007bff;
  --csv-importer-color-primary-light: #66b3ff;
  --csv-importer-color-primary-extra-light: #cce7ff;
  --csv-importer-color-secondary: #6c757d;
  --csv-importer-color-tertiary: #17a2b8;
  --csv-importer-color-tertiary-light: #5bc0de;
  --csv-importer-color-success: #28a745;
  --csv-importer-color-danger: #dc3545;
  --csv-importer-color-danger-light: #f5c6cb;
  --csv-importer-color-danger-extra-light: #f8d7da;
  --csv-importer-color-warning: #ffc107;
  --csv-importer-color-info: #17a2b8;
}

These variables allow you to adjust the color scheme for primary, secondary, and tertiary elements, as well as status indicators such as success, danger, warning, and info.

License

MIT © czhu12

Development setup

  • Run npm i && npm run watch - this watches for changes in main plugin and rebuilds if needed
  • Run cd example && npm i && npm run dev - this runs example application

To run no build example you could run

  • npx http-server .
0.1.0

3 months ago

0.1.2

3 months ago

0.2.0

3 months ago

0.1.1

3 months ago

0.1.4

3 months ago

0.1.3

3 months ago

0.1.5

3 months ago

0.0.10

2 years ago

0.0.11

2 years ago

0.0.3

2 years ago

0.0.9

2 years ago

0.0.8

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.7

2 years ago

0.0.6

2 years ago

0.0.2

4 years ago

0.0.1

4 years ago