React Implementation Example
This implementation example should help you gain some insights about our importer and support you in estimating the effort of integrating the <NuvoImporter>
into your web app.
To get started, you need a developer license key, which you find in our User Platform. If you have no account yet, book a Demo to receive access.
To integrate the nuvo importer in your web app, you need to do the following 4 steps:
- Define the importer settings
- Create your target data model
- Define how data should be manipulated during import by writing cleaning functions
- Install the package & implement the code snippet into your React app
This guide will walk you through each point and explain how the setup is done by using a CRM data import as an example.
The user-facing frontend of the <NuvoImporter>
contains the following steps:
- Upload: Here, the user can upload its file(s) by either dragging its file(s) within the dropzone or pressing the import button. We currently support .xls, .xlsx, .csv, .tsv, .xml and .json files.
- Sheet Selection: If the uploaded file has more than one sheet, the user has to select the sheet(s) with the data that matter for the import.
- Header Selection: The user must select the header row containing the column names for each imported sheet.
- Match Columns: The imported columns are matched to the columns of the target data model.
- Review Entries: In this step, the user reviews data, cleans errors and finishes the data import.
1. Define importer settings
To create a fully working nuvo importer within a React app, copy the code snippet below. Of course, you can add additional settings, but they are optional and mainly used for further customization.
The implementation code gives you the frame you need to start immediately. In the other sections, we will add additional code to the snippet to meet the complexity of our CRM example.
<NuvoImporter
// Insert your dev or live license key here. You can find your license key within your user platform account.
licenseKey="<Your License Key>"
settings={{
// If you are using a dev license key, developerMode must be set to true.
developerMode: true,
// Define an identifier for this importer.
identifier: "crm_data",
columns: [
// Here you define your target data model (See section 2).
],
}}
// With the onResults function, you can define what to do with the user-submitted data.
onResults={(result, errors, complete, logs) => {
console.log("Result:", result);
console.log("Error rows:", errors);
console.log("Logs:", logs);
complete();
}}
// With the onCancel function, you can define what to do after the importing process has been canceled by the user.
onCancel={() => {
console.log("onCancel");
}}
// With these three cleaning functions you can define data transformations which will be executed during the importing procedure (See section 3).
columnHooks={{
company_id: (values) => {
return values.map(([item, index]) => [{ value: item }, index]);
},
}}
onEntryChange={(rows, logs) => {
return rows.map((row) => {
return {
rowIndex: row.rowIndex,
data: { company_id: { value: row.data.company_id } },
};
});
}}
onEntryInit={(row, rowIndex) => {
return { company_id: { value: row.company_id } };
}}
/>
2. Create a target data model
With the target data model, you define in which format you want to receive the imported and submitted data. You find the detailed documentation here. You can create your custom target data model from a .xlsx or .csv file by using the upload button within the "Setup" tab in our User Platform or use our target data model creator tool.
In our example, we work with CRM data.
columns: [
// Each entry represents one column of your data model.
{
// Technical naming. Used as key for the output JSON object.
key: "internal_identifier",
// Displayed column name
label: "Internal Identifier",
// Type of the output values of this column. Can be either "string", "int", "float", "boolean" or "category".
columnType: "string",
// A descriptive text that is displayed if the user hovers with its mouse over the info icon next to the column name.
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
},
{
key: "company_id",
label: "Company ID",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
// A unique validation will lead to a duplicate check for the values of this column. The duplicate will be flagged red in the "Review Entries" step.
validate: "unique",
},
],
},
{
key: "company_name",
label: "Company Name",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
// You can also define dependencies between columns. They will be checked in the "Review Entries" step. If you want to create more complex column-cross dependencies, you can use our three cleaning functions.
validate: "required_with",
columns: ["company_id"],
},
],
},
{
key: "company_domain_name",
label: "Company domain name",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "required_with_all",
columns: ["company_id", "company_name"],
},
{
// Custom regex with a customized error message to check if the domain name is provided in the right format
validate: "regex",
regex: "(https?:\\/\\/|(www\\.)){1}?[-a-zA-Z0-9@:%.\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%\\+.~#?&//=]*)",
errorMessage: "Please use one of the following formats beginning with: https:// or www.",
},
],
},
{
key: "phone_number",
label: "Phone Number",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
// A required column needs to be matched in the "Review Entries" step (See the screenshot below).
validate: "required",
},
{
validate: "regex",
regex: "(\\+(9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)|00)\\d{3,14}$",
errorMessage: "Please use one of the following formats beginning with +XX or 00XX.",
},
],
},
{
key: "street",
label: "Street",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
},
{
key: "city",
label: "City",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "required",
},
],
},
{
key: "country",
label: "Country",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "required",
},
],
},
{
key: "costs",
label: "Costs",
columnType: "float",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
// Define keywords/synonyms that will be used inside the "Match Columns" step to improve the automatic column mapping.
alternativeMatches: ["Expenditures"],
validations: [
{
validate: "required",
},
],
},
{
key: "email_address",
label: "Email address",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "required",
},
{
validate: "unique",
},
{
validate: "regex",
regex: "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])",
errorMessage: "Email",
},
],
},
{
key: "amount",
label: "Amount",
columnType: "int",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "required",
},
],
},
{
key: "close_date",
label: "Close date",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
validations: [
{
validate: "regex",
regex: "^(?:(?:31(\\.)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\\.)(?:0?[13-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$|^(?:29(\\.)0?2\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0[1-9]|1\\d|2[0-8])(\\.)(?:(?:0[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{4})$",
errorMessage: "Please use a date format like the following one: dd.mm.yyyy",
},
],
},
{
key: "day",
label: "Day",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
},
{
key: "month",
label: "Month",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
},
{
key: "year",
label: "Year",
columnType: "string",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
},
{
key: "department",
label: "Department",
columnType: "category",
description: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam",
// A category column must have dropdown options. The input data will be mapped to these options inside the "Match Columns" step.
dropdownOptions: [
{
label: "HR",
value: "hr",
type: "string",
},
{
label: "Sales",
value: "sales",
type: "string",
},
{
label: "Marketing",
value: "marketing",
type: "string",
},
{
label: "Product",
value: "product",
type: "string",
},
{
label: "Design",
value: "design",
type: "string",
},
],
},
];
All these defined columns will be available in the "Match Columns" step, where the columns of the imported file are matched with the columns of the target data model.
Validations
and columnTypes
will impact the data in the "Review Entries" step. If specific values of the imported data are not valid, errors are displayed to the user, and the user must correct them.
3. Define how data should be manipulated during import by writing cleaning functions
This is the most time-consuming step during the setup of the NuvoImporter
. You can write custom data manipulation functions for each use case. Our extensive cleaning function knowledge base within our user platform will help you to speed up the implementation.
You can use cleaning functions to do server calls (for example, fetch data from your backend or validate data with external APIs), modify imported values or create column-cross dependencies. In our example, we use onEntryInit
and onEntrychange
. You find more details about cleaning functions here.
// This cleaning function is triggered, if the user confirms the mappings inside of the "Match Columns" step and before entering the "Review Entries" step.
onEntryInit={(row, rowIndex) => {
return {
// Creates a warning for costs values above 1 Mio.
costs: {
value: row.costs.replace(".", ","),
info:
row.costs > 1000000
? [
{
level: "warning",
message:
"The uploaded cost value IS very high. Please check.",
},
]
: [],
},
// The three columns (day, month & year) are filled by the cleaning function. The close_date column is split up into these columns.
year: {
value: row.close_date.substring(6),
info: [
{
message: "This field has been formatted.",
level: "info",
},
],
},
month: {
value: row.close_date.substring(3, 5).replace("0", " "),
info: [
{
message: "This field has been formatted.",
level: "info",
},
],
},
day: {
value: row.close_date.substring(0, 2).replace("0", " "),
info: [
{
message: "This field has been formatted.",
level: "info",
},
],
},
// This will merge two columns into one
internal_identifier: {
value: row.company_id
? row.company_id + " - " + row.company_name
: "",
info: row.company_id
? [
{
message: "This field was automatically generated.",
level: "info",
},
]
: [],
},
};
}}
// The internal identifier was already filled by the onEntryInit function. The additional onEntryChange function is triggered on a specific row, whenever the user changes a value in that row.
// In this case we make sure that the internal_identifier field is updated after the user changes the company id or the company name.
onEntryChange={(rows, logs) => {
return rows.map((row) => {
return {
rowIndex: row.rowIndex,
data: {
internal_identifier: {
value: row.data.company_id
? row.data.company_id + ' - ' + row.data.company_name
: '',
info: row.data.company_id
? [
{
message:
'This field was automatically generated.',
level: 'info',
},
]
: [],
},
},
};
});
}}
Here you see the result of the above cleaning functions in the Internal Identifier
Column.
4. Install the package & implement the code snippet into your React app
For this guide, we use a simple React app.
If you have created a React app, you have to install the nuvo React package via NPM or Yarn:
npm install nuvo-react
yarn add nuvo-react
Afterwards you can import the nuvo React library and copy the following code snippet into your App.js file:
import "./App.css";
import { NuvoImporter } from "nuvo-react";
function App() {
return (
<div className="App">
<NuvoImporter
// Insert your dev or live license key here. You can find your license key within your user platform account.
licenseKey="<Your License Key>"
settings={{
// If you are using a dev license key, developerMode must be set to true.
developerMode: true,
// Define an identifier for this importer.
identifier: "crm_data",
columns: [
// Here you define your target data model (See section 2).
],
}}
// With the onResults function, you can define what to do with the user-submitted data.
onResults={(result, errors, complete, logs) => {
console.log("Result:", result);
console.log("Error rows:", errors);
console.log("Logs:", logs);
complete();
}}
// With the onCancel function, you can define what to do after the importing process has been canceled by the user.
onCancel={() => {
console.log("onCancel");
}}
// With these three cleaning functions you can define data transformations which will be executed during the importing procedure (See section 3).
columnHooks={{
company_id: (values) => {
return values.map(([item, index]) => [{ value: item }, index]);
},
}}
onEntryChange={(rows, logs) => {
return rows.map((row) => {
return {
rowIndex: row.rowIndex,
data: { company_id: { value: row.data.company_id } },
};
});
}}
onEntryInit={(row, rowIndex) => {
return { company_id: { value: row.company_id } };
}}
/>
</div>
);
}
export default App;
And then you are ready to go. If you follow these four steps, you have a smart and fully working file importer with integrated cleaning functions to import .xls, .xlsx, and .csv files to your web application. If you have questions or feedback, please get in touch with us at [email protected] or book a call with our team.