Progress Indicator with Livewire

We are shown an image with a background of pastel, green color. Encompassing most of the image's center is a seemingly electric stove, on top of which chunks of brown cube ingredients are cooked in a grey frying pan. On the body of the stove, right below the cooking frying pan, is a label and a progress bar indicating the term LOADING.
Image by Annie Ruygt

Today we’ll implement a progress indicator using Livewire. Fly your app close to your users with Fly.io, get your Laravel app running in minutes!

In the article Chunked File Upload with Livewire we easily uploaded a file in separate, smaller chunks using Livewire’s upload function. Today, we’ll create a progress bar indicator to let our users know how much progress has been made uploading the file.

Version Notice. This article focuses on Livewire v2, details for Livewire v3 would be different though!

What’s in a Progress?

We can imagine progress as an accumulation of “small wins” leading up to a certain expectation. It is a portrait that summarizes how far we’ve come from completing our little wins towards an “expected goal”.

Progress can be measurable—if we can identify our “expected goal” and list down “small wins” that add up to it.

Today, the progress we’re calculating is the successful upload of each chunk out of the total chunks of a file to upload. Each chunk uploaded is a small win towards completely uploading a file.

Our calculation would be as follows: Progress Percentage = “Number of Small Wins So Far” / “Expected Total Wins” * 100%.

This means we’ll need a $chunkCount to let us know how many chunks we’re expecting ( Expected Total Wins ), $uploadedCount to keep track of how many chunks have been successfully uploaded ( Number of Small Wins so Far ), and finally, a separate $progressPercentage for our progress bar to use ( Progress! ).

Stepping Towards Progress

In order to display progress of separate wins for our file upload, it’s important to accumulate each win and update our client with the total progress.

This is where Livewire comes in. Livewire provides us with just the right bridge of data sharing mechanism to keep track of the completed chunk uploads, and data lifecycle hooks to recalculate our progress and make implementing a progress bar a breeze:

  1. We’ll want to automatically share progress variables between client and server, so we declare public attributes $chunkCount, $uploadedCount, and $progressPercentage.
  2. We’ll use a progress bar element in our html as our progress indicator and bind its value with the value of $progressPercentage
  3. Every time a chunk gets successfully uploaded, we intercept this upload completion in the server by using Livewire’s updated hook. From there we can increment our $uploadedCount, and recalculate the $progressPercentage right before the Livewire component sends back a success response to the Livewire view. Once the Livewire view receives this update in our $progressPercentage‘s value, it should trigger a re-rendering of the <progress> element bound to our attribute, thereby updating the displayed progress for our users to see!

The Indicator of Progress

Progress is calculable, and so we’ll first need to declare variables we’ll be using in calculating this value in our Livewire component:

// app/Http/Livewire/ChunkedFileUpload.php

public $chunkCount;
public $uploadedCount;
public $progressPercentage;

We’re building on top of the setup of our article here, where our users can select their file and click on a button to trigger a custom JavaScript function uploadChunks() to slice and upload chunks from their selected file:

<!-- app/resources/views/livewire/chunked-file-upload.php -->

<input type="file" id="myFile"/>
<button type="button" id="submit" onclick="uploadChunks()">Submit</button>

Every time a file gets submitted for upload, we’ll initialize our progress variables to correspond to a new file’s progress. uploadChunks() is triggered every time our user requests for a file upload, so let’s add our progress re-initialization here:

<script>
...
function uploadChunks(){

    // File Details
    const file = document.querySelector('#myFile').files[0];
+   @this.set('chunkCount', Math.ceil(file.size/@js($chunkSize)));
+   @this.set('uploadedCount', 0);
+   @this.set('progressPercentage', 0);
    ...
}
</script>

Next, let’s add in a progress bar in our Livewire view. We’ll wire it with the $progressPercentage attribute declared above. This way, any change happening on the $progressPercentage attribute will refresh and calculate the value of our progress bar.

<!-- app/resources/views/livewire/chunked-file-upload.php -->

<input type="file" id="myFile"/>
<button type="button" id="submit" onclick="uploadChunks()">Submit</button>

+ @if( $progressPercentage  )
+  <progress max="100" wire:model="progressPercentage" /></progress>
+ @endif

Now that we have our progress bar dancing to the tune of the $progressPercentage attribute, we’re ready to update this progress every time we complete a small win—that is, every time we successfully upload a chunk of our file.

Fly.io ❤️ Laravel

Fly your servers close to your users—and marvel at the speed of close proximity. Deploy globally on Fly in minutes!

Deploy your Laravel app!

This is How we Progress

We’ll update our progress every time a chunk gets successfully uploaded. In order to do so, we’ll have to answer two questions—how do we know when a chunk completes uploading, and how do we react to this completion?

We’re building over a setup that uses Livewire’s upload function to upload our file chunk:

@this.upload('fileChunk', chunk, ...);

Notice the attributes passed in the function: chunk is the JavaScript variable that holds reference to a chunk of our file, while fileChunk maps to a public attribute in our Livewire component declared as $fileChunk.

When this function runs, the Livewire view does a bunch of uploading process for the variable chunk into a temporary folder in our server. Once it completes uploading the file chunk, it then updates the mapped attribute $fileChunk with details on the uploaded chunk. Livewire conveniently provides us the updated hook which we can use to inject logic after this “update” happens on our variable.

From this hook, we can recalculate the value of $progressPercentage. Every time a chunk gets uploaded, we increment the number of chunks uploaded $uploadedCount. Then we’ll divide this by our expected finish—the total number of chunks: $chunkCount, and finally multiply it by a 100 to get our $progressPercentage:

public function updatedFileChunk()
{
    // Update progress here
    $this->uploadedCount += 1;
    $this->progressPercentage 
    = ($this->uploadedCount / $this->chunkCount) * 100;
    ...
}

Our updatedFileChunk() hook recalculates and updates our $progressPercentage right before the Livewire component responds to the client regarding the successful upload.

The change sent back from our Livewire component on the $progressPercentage refreshes the <progress> element bound to it with a new value, moving our progress indicator.

That’s it! Try uploading your file now, and see that progress bar moving towards a 100!