Over the past week, I’ve been working on a front end file upload form component. We want to be able to upload a .txt file and then read the contents to validate the formatting. After a bunch of testing, I was able to get the validation working the way I wanted it to, but there was one persistent bug I couldn’t get rid of.
When I attempt upload a file with invalid text, my component throws an alert and the upload remains incomplete. So if I open the file, fix the formatting, and attempt to re-upload, nothing happens. Though the contents of the file have changed, the HTML doesn’t recognize an onChange
because the file name is unchanged. Though this is expected behavior for HTML, is there a way around it?
Recreating the issue
If you want to follow along at home, we can set up a simple example with React. First, create a React project:
npx create-react-app onchange-practice
Once that’s ready, let’s create a simple file upload form in App.js. Here’s an example I found on filestack:
import './App.css';
function App() {
return (
<div className="App">
<form>
<h1>React File Upload</h1>
<input type="file" />
<button type="submit">Upload</button>
</form>
</div>
);
}
export default App;
At work, I used formik to add my validation. Since this is a simplified example, let’s create a handleChange
method that just logs the contents of the file:
const handleChange = event => {
const reader = new FileReader();
const file = event.target.files[0];
if (file) {
reader.readAsText(file, "UTF-8");
reader.onload = readerEvent => {
const fileText = readerEvent.target.result
console.log(fileText);
}
}
}
We use the a JS FileReader object to access the contents of the file from the event
, which will be passed by an onChange
event. If a file is found (it won’t always be found), readAsText
kicks off a process of reading the file. Finally, onload
appears to be an async function that will spit out the log I’ve programmed once it’s ready.
To actually trigger the function, we just drop it into the HTML:
<input type="file" onChange={handleChange}/>
If we run npm start
, we should see the behavior:
Notice we get a log the first time, but not the second time. After all, nothing “changed” as far as HTML is concerned. But the file is different and we want to reflect that; how can we do it?
Enhance our handleChange function
If our file is invalid, we can write JS that will detatch it from the input
element:
const input = document.getElementsByTagName('input')[0];
input.value = null;
By making the input value null
, the file is no longer reflected as being uploaded to the page. Let’s decide on a condition for file validity and program that into onChange
:
if (!fileText.includes('different')) {
console.log('THIS FILE IS INVALID');
const input = document.getElementsByTagName('input')[0];
input.value = null;
}
And we can try the process one more time:
Success! A simple fix for a simple example. I couldn’t get this to work with formik because I didn’t have access to the literal onChange
method or the HTML elements, but this was still a good learning experience. The StackOverflow response I was following smartly pointed out that the reset should go at the end of onChange
instead of being triggered with an onClick
because it’s possible to interact with the input
element without clicking (fully through keyboard). There are definitely advantages to libraries that abstract away simple processes, but we do lose some control when that happens, costing us the ability to make little customizations like this.
Sources
- React File Upload, filestack
- formik
- FileReader, mdn web docs
- HTML input file selection event not firing upon selecting the same file, StackOverflow
- Reading file contents on the client-side in javascript in various browsers, StackOverflow