Previewing a CSV file with VueJS and Papaparse

Part 7 of 7 in Your Guide To Uploading Files with VueJS and Axios
Dan Pastori avatar
Dan Pastori November 1st, 2021

There are so many use cases for allowing a user to upload a CSV file into your application. However, there are equally as many problems with CSV file uploads such as empty columns, bad data, etc. These issues are why this is my favorite tutorial of the Uploading Files with VueJS and Axios series. This tutorial solves this problem using a fantastic open source library called Papaparse.

With Papaparse, we can actually allow the user to preview their CSV upload and make changes to the data BEFORE submitting the file to the server. Papaparse even gracefully handles empty lines and headers. While there is so much that Papaparse can do, we are going to touch on a few scenarios. I’d highly recommend checking out their documentation to see the library’s full feature set.

If you want to cut straight to the chase, you can check out our GitHub repo for this tutorial and view the component that handles parsing and previewing a file. Let’s dive in!

Step 1: Install Papaparse

The first thing we need to do is install Papaparse. To do this run:

npm install papaparse --save-dev

After NPM completes, Papaparse will be ready for use!

Step 2: Include Library In Component

Now that we have Papaparse installed, we can add it to our VueJS component. To do this, import it into your component right next to where you’d import Axios:

import Papa from 'papaparse';
import axios from 'axios';

We are ready to go! Let’s upload a CSV File.

Step 3: Handling CSV File Uploads

We’ve stepped through how to upload a file a few times, so if you haven’t read the article about uploading a single file, I’d recommend checking that out first. We will be expanding on a few of the methods in that tutorial.

The first thing we want to do is accept only CSV files in our input. To do this, apply the proper accept property to your file input:

<input type="file" accept=".csv" @change="handleFileUpload( $event )"/>

Now when you try and select a file, we will limit the input to only selecting CSV files. Remember, always validate this server side IF you are sending the CSV file to the server (we might not actually need to!).

Before getting too far ahead of ourselves, add the following pieces of data to your component:

data(){
    return {
        file: '',
        content: [],
        parsed: false
    }
},

The file data will look familiar. This will house the File object that the user selected. However, the content and parsed will be new. The content will contain the JSON representation of the CSV file. See how we might not even need to send the file? More on this in a few. The parsed piece of data is simply a flag to let the component know that Papaparse has parsed all of our CSV. We will use this to determine when to render the information.

Next, check out your handleFileUpload( event ) method (See Uploading Files with VueJS and Axios). Right after we set the file, add a call to this.parseFile() :

handleFileUpload( event ){
    this.file = event.target.files[0];
    this.parseFile();
},

We will implement this method next.

Step 4: Parsing A CSV File

Papaparse makes this a breeze! To do this, we will implement our parseFile() method. To implement this method, add the following to your methods:

parseFile(){
    Papa.parse( this.file, {
        header: true,
        skipEmptyLines: true,
        complete: function( results ){
            this.content = results;
            this.parsed = true;
        }.bind(this)
    } );
},

There’s a lot to unpack in this method. First, we are calling the parse method on the Papa instance. The first parameter is the this.file that is the CSV file that we uploaded. We can pass a direct reference to the file object and Papaparse will take care of the parsing!

Our second parameter is an object that contains our settings for how we want our file to be parsed. For a complete list of settings, check out the Papaparse documentation.

The header setting that we use tells Papaparse that there will be a header in the CSV. With a header present, the field name of the object will be the name of the column. For example if we have a Title column in the header, the corresponding value will be row.Title. We will get into this a little bit later.

The second setting is skipEmptyLines. This is super powerful boolean that will ignore any empty lines in the CSV. Some CSV files are poorly formatted and include a few extra lines which can disrupt the validations. If you read in a CSV file with Papaparse and allow the user to edit the JSON, then you can eliminate a lot of the empty line woes.

The final setting is actually a callback for what to run when the file has completely been parsed. In this case, we bind the local component’s data to the function and set this.content = results. The results of the parse will be set to our local content variable allowing us to display this to the user. The this.parsed = true flag will also be set so we can show the data to the user.

Now that we have our data parsed, let’s perform some magic and display it to the user to allow them to make changes before submission.

Step 5: Managing Data Before Submitting

This is where the payoff really begins to happen. You can allow the user to make changes to the data from the CSV BEFORE submitting it to the server. This will prevent unnecessary submissions and invalid data resulting a smooth user experience.

I do want to point out, in the example in GitHub, I created a way where it will display whatever is uploaded. Any CSV file can be selected and displayed. However, I believe that in most circumstances, there will be a pre-defined format on how the data should be displayed. You won’t really have your users uploading random CSV files, there will be a format. The example shown will cover this base as well.

The first piece of code to add is to your template. Remember, we already parsed the CSV file and saved the results to the this.content variable. Let’s display that information. In our component we added the following table:

<table v-if="parsed" style="width: 100%;">
    <thead>
        <tr>
            <th v-for="(header, key) in content.meta.fields"
                v-bind:key="'header-'+key">
                {{ header }}
            </th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="(row, rowKey) in content.data"
            v-bind:key="'row-'+rowKey">
                <td v-for="(column, columnKey) in content.meta.fields"
                    v-bind:key="'row-'+rowKey+'-column-'+columnKey">
                        <input v-model="content.data[rowKey][column]"/>
                </td>
        </tr>
    </tbody>
</table>

What this does is displays the CSV file in a table format. Let’s start with the breakdown of what is returned when parsing a CSV File.

There are 3 keys that get added to your content variable, meta, data, and errors. We use meta and data within our display. The errors field will have any of the parse errors that Papaparse encountered.

Breaking Down The Parsed Results

The meta field consists of information regarding the CSV. The most important part of the meta field is the fields key which is an array:

content.meta.fields

Since we are abstracting the header from the CSV file, this will be the name of each of the columns in the header (the first line of the CSV). If you look at the <thead> element in our template, we iterate over each of these fields and display the value of each piece of data. If you already know what each of these fields are, which is very likely, you can just create a header with that data already present.

Next, we have our data field. That field contains an object for each row that has the header as keys. If you look at our <tbody> we iterate over each piece of data in the content.data as a row. In our example, we also iterate over each column so we can dynamically generate a CSV. Once again, you could ensure that a certain format has been uploaded and won’t have to do this. The key to making this useful is to make an input for each field you want the user to be able to edit. In the example above, we want the user to be able to edit any field of the CSV they want. You can use the v-model directive and bind it to a piece of data within what was parsed from your CSV:

<input v-model="content.data[rowKey][column]"/>

Now the user can edit any piece of data in any field before submitting the data to the server! This can be extremely useful in a variety of different applications! Let’s submit that data next.

Step 6: Submitting Data to the Server

Since we parsed the data as JSON and allowed the user to update and make any necessary changes to the CSV data, we don’t want to send the raw file. We want to send the content.data. These fields were mapped to the input fields and we allowed the user to edit the data. To do this, add the following method:

submitUpdates(){  
    axios.post( '/preview-file-changes',
        this.content.data
    ).then(function(){
        console.log('SUCCESS!!');
    })
    .catch(function(){
        console.log('FAILURE!!');
    });
}

Now when the user is done editing their data, we can submit it to the server and do whatever sever side processing is necessary!

Conclusion

I really love the functionality that Papaparse adds. The ability to preview and edit a CSV file BEFORE uploading is essential to ensure data integrity. This comes in really handy when developing a Single Page Application as well since you can do a lot of the work up front if needed. We use Papaparse with VueJS when importing a CSV of transactions in our budgeting app Financial Freedom. I find it’s really helpful to give that preview and allow the user to tag and manage transactions before uploading them to the server.

If you are interested in more tips and techniques about developing a single page application with an API backend, we wrote a book, check it out. The book goes through a lot of other helpful information and uses VueJS as well.

Of course, if you have any questions, feel free to reach out in the comment section below!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Want to learn more about API Driven App Development?

We're writing a book on API Driven App Development. Be the first to know once it is ready!