15 - Download functionality
Last updated
Last updated
In the previous section (section 14), we've finished the upload functionality of the file.io app. That covers one half of the app. In this section, we'll finish the download functionality, the other half of the app.
Just to recap, the download functionality is:
On page load, make an API call to the server using the file ID from the URL
If the server returns success, show file details and a download button
If the server returns 404, show the file not exists error
When the download button is pressed, get the file from the server (server removes it from the storage as soon as the response gets sent)
A small anime of the vanilla download functionality is:
In section 8, we've already decomposed the file.io app into a number of small & manageable components. Here is the diagram again:
Just like upload, the Header, MainDownload, and Footer are always visible. If server API results in failure, then FileError component will be displayed. If server API succeeds (meaning that file exists), then FileDetails & FileDownload components will be displayed. The FileDetails component is the same one that we've seen in upload functionality. This is the power of creating small, manageable, and reusable components.
Note: Header, Footer, UploadAnother, and FileDetails components have been reused in download functionality. Their code is not shown in this section. You can see their code in section 14.
The Download component code is very straightforward:
Download.js
The Header & Footer are the same components that we've used in the upload functionality. The fileId parameter is coming from the React router param extraction. To get a quick recap, you can visit section 10. The fileId parameter gets forwarded to the MainDownload component.
Just like the upload case, where the state was maintained in the MainUpload component, in the download case, the state is maintained in the MainDownload component. The state will contain the following data:
fileFound: true/false: Indicates if file has been found or not
fileData: This contains information about the file such as file name & size
The state will be initialized to { fileFound: false }
. This is the only thing that can go in state because other file attributes are not known till file has been found. Unless the server API confirms existence of the file, the file is considered to be not available. In other words, the default behavior is file no available.
This leads to some conditional rendering in the MainDownload component. If fileFound is false, then FileError will get rendered. If fileFound is true, then FileDetails & FileDownload gets rendered. In all cases, UploadAnother will get rendered. The UploadAnother component was conditionally rendered in the upload case, but is always shown in the download case.
The download functionality also needs to implement the concept of child updating parent's state. In the upload case, we saw that the state was maintained only by the MainUpload component. No other upload related components maintained any state. While MainUpload had the state, the file upload functionality was present in the FileUpload component. The design is the same with the download case. Only MainDownload maintains the state. No other download related components maintain any kind of state. The FileDownload component is responsible for getting the file downloaded. As soon as the button gets clicked, an API will be called to get the file. The server removes the file as soon as it gets served. Therefore, the FileDownload component needs to indicate to the parent that the file no longer exists as soon as it gets downloaded. This gets achieved through parent callbacks (just like we did it for the upload case).
The following is the code of the MainDownload component. As soon as the component gets rendered, useEffect hook gets invoked to fetch the file details from the server. This will be configured to run exactly once (using [] in dependency list). The result of the API call sets the state which trigger re-rendering of the MainDownload component. If the file has been found, the received file data gets passed to FileDetails & FileDownload.
MainDownload.js
If the code and the explanation doesn't make it clear, the following diagram will help:
The FileDownload component is responsible for getting the file to the user. As soon the file gets to the browser, the server deletes it. The FileDownload component immediately uses parent callback to indicate that the file no longer exists. The parent gets re-rendered and enables the FileError component in this render. Just like FileUpload offering functionality through a visible and a hidden button with click simulation, the FileDownload also offers functionality through a visible and a hidden button with click simulation. A regular styled button is shown to the user. But the download functionality is handled by HTML's built-in <a download> feature. Using React's useRef, a reference for hidden <a download> is created. This reference is clicked when the styled button gets clicked. To enable storing of the file on the disk, the received file needs to be converted to a data URL that goes into the <a download> element. Once the data URL is set, then only <a download> gets clicked programmatically. (this is a standard JS feature).
FileDownload.js
The only remaining component in the download functionality is the FileError component. This is a static component that shows an error.
FileError.js
FileError.css
Again, you may not realize that we've just finished the download part of the file.io app. Instead of having a big HTML and a big JS file, we now have very small pieces that clubs together to solve the download puzzle. Combining this with the upload puzzle, the complete puzzle is solved now. Congratulations!
To test it out, first start the server in a different terminal:
Second, start the webpack server in another terminal:
Open a browser and go to http://localhost:3000. First, we'll upload a file, then download it. This is our final end to end testing.
The upload & download functionality works as expected. In fact, there is hardly any difference from the vanilla app. We've finished migrating vanilla app to React!
--
That's all about it. In the next and the last section of this book, we'll go through some possible next steps for you.