Download Progress Bar in React with Fetch API

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
when giving your visitors the ability to download large files we often don't get as much control over the ux that we provide for that download so instead let's see how we can track download progress inside of a react app where we'll learn how to stream a response and update its progress inside of the app now before we dig in I have a course coming up where we're going to build a full stack react application using app right this includes authentication and basically cred like operations on a database so make sure you head over to spacejelly.dev react apparate and get updates to your inbox so we're going to start off with a simple interface inside of a react app where I have my little thumbnail here but ultimately I have a download link or button that when clicked what I want to happen is I want it to download that file but what we're going to get to is I wanted to be able to track the progress of this download now the way that this is currently set up is I have this link inside of an anchor tag where I have the download attribute where it's just pointing to this big video file of mine but one of the issues with the download attribute is you have to know its limitations where it's only going to work for same origin URLs has to be secure and while that makes sense isn't as fine it's not always reliable if all you want to do is provide an external CDN link for an actual download but as we can see if I actually try to click this download button it's going to just take me to that cloudnary file where the video is going to actually play rather than actually download in my browser so we have a few things that we want to accomplish here first of all we want to be able to download that file no matter what so what we're going to do is we're going to download it into the existing session and then we're going to download it as a blob but then what we also want to do is we want to track the progress of that download so while we're making that request we can actually listen to it as a stream and we can update that progress in the UI so let's start off by changing how we're currently downloading this we're currently I'm using an anchor tag with a link but what we're going to do is we're going to use a button element where we're going to say on click we want to actually download that file so on this button that I uncommented out I'm going to add my on click Handler and let's call this new function handle on click where I'm going to Define my new handle on click function where what I'm going to do is I'm going to await a new fetch request and what I'm going to fetch is that file URL so that I can download that file first as a blob now as we can see I'm using the await syntax so I want to also make this an async function as I just prefer that syntax but now that we're making that request let's first save that in a response object where then I want to grab the blob of that response so I'm going to say constant blob is equal to a weight response dot blob and let's console log out that blob just so that we can see what that looks like now I'm also going to get rid of that original anchor tag just so that we don't have to deal with that anymore but if I go back to the application and I click this download button we can see that the UI kind of seems like it's unresponsive right now if I click the network Tab and we scroll down we can actually see that this fetch request was getting made where it did go and grab that file so that means if I now go back to my console I can now see that blob logged out we can see the size we can see the type of that file and that blob is going to contain all the information for that file now just as a quick note I'm currently using a big video for this but what I'm going to do temporarily is change it to just a simple JPEG that we can download pretty quickly so that we're not wasting a ton of bandwidth trying to test this out now because I'm using a cloudinary video URL for this what I'm going to do is I'm going to add F jpeg meaning it's just going to turn this video into a single framed jpeg it's going to grab a frame that's the most interesting from the video where it's going to serve as my preview and just to kind of see what that looks like we can see that with this parameter it's going to grab that same you URL only with that format as a JPEG so that it returns that frame but as we can see now if I click that download button it's going to be much faster it's a much smaller side so we can see it's a JPEG that's just going to make a little bit easier for us to work with here so now that we have this blob we want to ultimately download it to our file system from within the browser session now the way that this is going to work is we're going to create a virtual anchor tag where we're going to virtually also click that anchor tag where that anchor tag is going to basically have a URL version of this blob so that when it's clicked virtually it's going to download that file for us because this virtual anchor tag has just the raw file data inside of it it's going to prompt it as a download so let's first create this URL for our blob so we're going to set a constant of URL and that's going to be equal to URL dot create object URL where we're going to pass in that blob now just to also see what that looks like we can constantly log that out where when we click the download link we can see that it kind of looks like just a local link for our application but that's what we're going to use to actually download that information so now that we have this URL let's use it to create an anchor tag where we can actually start to download that so I'm going to say constant a is equal to document create elements we're going to create an anchor tag we're going to say a.href is equal to our URL now even though the download attribute wasn't working for us on the anchor tag like we were expecting we still need to use that now where we're going to specify that download but what we can do now is we can also specify the file name of this file so we can just simply say something like file if you want you could also try to parse out the file name of this URL if you want to save that kind of file name as is but now that we have our anchor tag and we have our ahref pointing to that URL let's simply run anchor tag click so now if I click that download button we can see that file pop up right into the download queue now one thing to notice as we're creating this anchor tag and the length for it is we created this object URL and currently the browser is then storing that as a reference so what we can do is we can then revoke that object so that the browser no longer needs to know about it or keep it along for reference so the way that we can do this is we can listen to The Click using an event handler and then once that fires we can then revoke it so on my anchor tag I'm going to add event listener we're inside I want to listen to The Click Handler and I'm going to ultimately fire a function let's call this handle on download where I'm going to then Define that handle on download but I also want to pass in false at the end of my event listener now inside this Handler we can say now url.revoke object URL where we're going to pass in that URL we can also free up this event listener by adding a remove event listener on our click where we're going to pass in that handle on download as a reference to itself now if we head to the browser and try this we can actually see that we get an error here and it's saying check internet connection the issue is if we've triggered this revoke object URL too fast it's going to remove it before we even have a chance to download it so what we can do is we can set timeout we're inside we're going to pass a function we can say after 150 milliseconds but what we're going to do is we're going to add that logic inside of the set timeout where now if I click download again we can see that it still downloads that file for us and doesn't have to worry about it not being available okay so we got the first part out of the way where now we're currently downloading that file programmatically we're on click we're going to turn it into a blob and we're going to download that blob but what we want to do instead is we want to stream this response so that as we're downloading it we can actually start to feed that information into our application and update the progress into the state so we can show that within the UI so to start what we're going to do is we're going to actually first get get rid of this blob response where what we want to actually get is the response body and to start off to make sure that typescript is happy we're going to say if the response body does not exist we're going to return but let's first console log out this response body just to see what this looks like where let's also comment out some of the existing code that we have just to make sure that it's not going to throw an error for us but we can see if we click download we get this readable stream which is exactly what we want but we want to read that stream and take the information and pass it to our application now the first thing that we want from this body is something called a reader and that's going to help give us a tool in order to read the information so let's say constant reader is equal to response.body.getreader now what's going to happen is on this reader object there's going to be a function called read or a method rather where we're going to start to read the results from that reader so that we can then pass it into our application but to do that we need to make sure that we're constantly looking for that in a loop so that we can make sure we get all the info information before it actually finishes so what we're going to do is we're going to create a while loop and while this sounds weird we're going to pass in true so that it's going to just infinitely run but inside we're going to run awaitreader.read where we're going to destructure what's passed back where we're going to get a value of done as well as an item of value itself where this is going to tell us whenever that it's finished reading all the information but then the value is going to actually contain the information that we're going to save to the application now to start to make sure that this doesn't occur infinitely we're going to say if done we want to actually break out of this Loop if we want for testing purposes we can even add a console log in of done just to make sure that we kind of can visualize that but if it's not done we're going to be able to get this value information so let's actually start to console log this out to see what it looks like and if we click the download button we can see that we get a lot of information and depending on how we click this it's going to break it up in chunks and pass that back and as it's read it's going to pass it to the application for us we're finally once it gets all the information it's going going to say it's done now what we want to do is save each and every one of these chunks so what I'm going to do is create a new constant called chunks and set that to an array where after that done I'm going to chunks dot push that value where once we have all that chunk information that's ultimately going to be all the file information so what we can do is we can set constant of blob is equal to new blob where we're going to actually create a new instance of blob using that information or the chunks rather which was in my clipboard and now guess what we can do is we can pass that into our existing code for that blob so let's do this I'm going to click download and we can see the information passed into the console and we can see file but it's file.txt because it doesn't actually know the extension that we're creating when we're downloading and creating that new blob from scratch now this part's a little bit tricky as we don't really know what that file type is because we're just forming it through all those different chunks of data but you can do it a few ways where if you already know the file type you can set the extension manually if you're downloading for a resource from Clan Ray for instance you can actually find out what that file data is as you're grabbing it you know there's a few ways that you can do this you can grab the extension if you know that the extension is going to be on there but for now we can kind of consider that out of scope of what we're trying to accomplish here I'm just going to add JPEG to the end of this and be done with it where now if I try to download the file again we can see that it's downloaded as file.jpg and then I can open it up as my downloaded file Okay so we've kind of completed the second step of this where not only are we downloading the file we're assembling the pieces by taking little by little chunks and then we're able to put them into our grader file and finally download that so now what we're going to do is we're going to read the size of each and every one of these values and once we get that size we're going to compare it to the total size that we're going to get from the content editors where we're going to then update the application state so that we can show that so to start we want to know the total size that we're going to get from this file and not every single file is going to be able to give this to us where if we look at this image inside of our browser we can see that we're going to get this content length header and a lot of files should be able to pass this back for us but we're going to be able to see that we can handle in case that we don't get the entire information but we can see that this 96896 is going to be the total size of the file so we can track the progress of the information we have to that total size so I'm going to say constant content length is equal to response.headers get and I'm going to pass in content length now this content length as a header is going to come back as a string and we're going to want that to be a number value so what we're going to do is we're going to create a second constant called total length you could probably do this a little bit differently if you'd like maybe you can create one content like content but I'm going to do it this way we're going to say if type of content length is equal to a string I'm going to use and I'm going to say parse int my content length variable and like usual we can console log this out and we can see what this looks like or if we download we can see the very first thing that we got was that total length so next the way that we can get the size of this value is by simply adding length to the end of it which is going to give that size so what we're going to do is we're going to create a new left variable called received length where we're going to first set that to zero but every time we get a new one of these chunks we're going to say receive length is equal to receive length plus value.length so we can keep appending that size to our existing information that we're storing now once we have that we can start to inspect this compared to our total length where let's call it something like constant step is equal to our receive length divided by that total length and if you use typescript you could probably already foresee what's going on where we don't know that total length is going to be a number because that total length could be undefined and we want that to be undefined just so that we can make sure that we know that it's not going to be a number it's not going to be a valid zero if that's the case so what we're going to say if type of total length actually is a number then we're going to perform this check or division rather of grading that step but now we can actually turn this into a percentage by multiplying it by 100 that way we can now say that we have this value out of 100 which is going to give us that percentage as usual let's see what this step actually looks like I'm going to click download and we can see that while it occurred in only a few steps because of the file the size of the file we can see that it was able to give us the progress on that download just to see what this would look like if I remove that format and change it back into a video I can click the download and we can see that we're going to get a lot smaller steps as it's working through that another trick is you can also throttle your data that way it's going to actually try to download that much slower so if I keep that jpeg it's going to throttle it to a 3G network and we can see that while I still don't get as much steps as that video we can see that I get a lot more so I can visualize that better so now that we have this let's actually start updating the state so that we can get this visualized on the page so to do this we're going to use react state so I'm going to import use States from react we're inside of the application or at the top of my app file I'm going to say constant progress set progress is equal to use state if I can type now we can set that to zero since we know that that's going to be zero we can also make sure that we type this as a number so now every time we get this step let's actually run set progress where we're going to pass in that step now inside of my UI I already have some places where I'm visualizing this progress including this area where I have zero percent complete so I'm going to change that to progress complete and I also have this fancy animation that if you want to grab that yourself it's using Tailwind you can find it inside of the source inside of the description but I just had this set to 100 just as a way to temporarily store that value without removing everything so I'm going to update that to progress where similarly I have another instance where the other was the class names and this is going to be the left which is actually going to move the progress bar which I'm going to also set to my progress which I think that's actually going to be 100 minus progress so depending on what you're doing you want to make sure you create the math that's going to move that for you but now with the still throttled in 3G if I click download we can see that it's going to show me all that progress up to the 100 complete now if I do that again though you'll notice that there's a lot of values in there we can see that there's a lot of decimals we can probably see that even more clearly I'm going to throttle it even more we'll do slow 3G where if I click download it should download it again where we start to see that progress again where we probably don't need all those decimals and we probably don't need all those updates so to start on this progress where I'm showing the percentage I can use something like two fixed where I can just make sure that it's available before I run that and to start that should at least remove the decimal places from that file download but then we can also start to think about how we're actually setting that progress to begin with and letting react to try to take control of how often it's updating that state so how about let's just only store maybe two decimal places to begin with inside of the state itself so I'm going to start off by saying two fixed as two where it's only going to give me two decimal places but I still want this to be a number as what that's going to do is return a string so I'm going to still wrap that with parse float so what it's going to do is it's going to turn it into a string with two decimal places and then ultimately turn it back into a number where I can multiply it by 100 and get my percentage and if I click download we can see that it still should be working as expected but even so we probably don't need to update it for every single step and while you probably could we can try to keep performance in mind it really doesn't need to make that many updates into the actual browser so how about let's add some throttling to this now there's a lot of different ways that you can do throttling you can write your own which I probably don't recommend as it's complicated there's other packages involved in this but honestly the throttle that low Dash included is really good and we can really just install just that one throttle function from low Dash without having to include everything so it still becomes a viable option for being able to grab a good throttle function so I'm going to npm install throttle inside of my project I'm going to then import the function so I'm going to import throttle from that package and then I'm going to create my throttled function so I'm going to say constant update progress is equal to throttle where what I'm going to do is pass in a function to that throttle where before we do that the second parameter is going to be how much we want to throttle in let's say half a second we can do 500 milliseconds and you can tune this to whatever you like as you're working through your application and then we can optionally pass in some additional options including leading true and trailing of true which what that's going to do is going to say whenever this throttling happens I once you initially send a update no matter one and I want to finish with an update no matter what because depending on how it's getting throttled it might miss one of those within the throttling action but ultimately in this update progress function I want to set the progress so I'm going to say my value is going to be a number which I'm going to pass to this and then I want to set my progress where I can pass in that value now of course in order to use this throttled function we now need to update our reader in order to set the progress using update progress but now if we start to download we can see that once it starts to kick in it's going to give us a lot less updates which you know depending on what you want might be better because it might perform less updates within the react application which might be better for performance as I was saying you can configure this value to whatever you want for how often you want those updates to occur maybe you just want to throttle it 100 milliseconds so that it's still not updating literally every single event but it's still updating most of them so for instance with that 100 milliseconds we can see that once it kicks off that download it's still going to give us a lot of those updates but these are the types of things that you can play around on your own as you're building your own progress indicator so that you can make sure that you're balancing that user experience along with the performance of the application as we're building applications for people to use it's important that we're balancing that performance and the information that we're giving them to ultimately create as best of an experience as we can if you want to dig in further into how to build a full stack reacts application I have a course coming up soon soon where we'll use operate in order to build and manage databases and authentication inside of react so head over now to spacejelly.dev react apparate to make sure you can get updates right to your inbox if you want to continue improving on that ux such as trading a warning if somebody tries to hit back during that download check out my video where we learn how to use the before unload event listener in order to trigger that warning I know you learned something from this video so make sure you hit thumbs up subscribe and click that little notification Bell for more web dev content thanks for watching
Info
Channel: Colby Fayock
Views: 6,146
Rating: undefined out of 5
Keywords: download progress bar, download progress, download progress in javascript, download progress bar javascript, download progress react, progress bar react, progress bar react js, progress bar react example, progress bar javascript, progress bar fetch javascript, fetch streams, fetch api stream, fetch api javascript, fetch api react, react throttle, react throttle function, react throttle state, throttle react, lodash throttle react, react lodash throttle
Id: PbqmCsQsMXc
Channel Id: undefined
Length: 19min 37sec (1177 seconds)
Published: Thu Aug 31 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.