Reputation: 3443
I want an event or get notified when the user clicks on the cross icon or the cancel button on the fileInput dialog so that I can perform some task based on this.
The onChange event is only triggered when the user clicks on Open Button.
Is there a way to get notified when the user clicks on a cross icon or cancel button using ReactJS?
Below is the working code snippet.
class Test extends React.Component {
constructor() {
super();
this.fileInputClicked = this.fileInputClicked.bind(this);
}
fileInputClicked(event){
let file = event.target.files[0];
console.log("File is Selected", file);
}
render() {
return (
<div>
<div>
<p>Hello world</p>
<input type="file" onChange={this.fileInputClicked}/>
</div>
</div>
);
}
}
ReactDOM.render(<Test/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app' />
Upvotes: 6
Views: 10917
Reputation: 1082
There is a special cancel
event for that: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#detecting_cancellations
Unfortunately, React (at least 18.3) doesn't support onCancel
prop for input
elements. But you can listen to this event manually.
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
const fileInput = fileInputRef.current;
if (!fileInput) {
return;
}
const onCancel = () => {
// ... anything you want to do ...
};
fileInput.addEventListener('cancel', onCancel);
return () => {
fileInput.removeEventListener('cancel', onCancel);
};
}, []);
return (
<input ref={fileInputRef} type='file' />
);
Upvotes: 0
Reputation: 7092
This is an old question, but I needed an answer for it, too. However, the solution is slightly more complex than @Matan-Bobi suggests.
click
event is triggered when the Browse button is clicked, before the File Dialog opens.focus
event is triggered after the File Dialog is closed, in both cases. That is, it is triggered by a click either on the Open button or on the Cancel button in the File Dialog.change
event is triggered a few milliseconds after the Open button is clicked. It is never triggered if the Cancel button is clicked.Therefore, to detect if the Cancel button was clicked, you must wait a few more milliseconds to be sure that no change
event has been triggered. In my tests, 100 milliseconds seems to be a good length of time to wait.
My code below provides Too Much Information (especially if you uncomment the line in the componentDidUpdate
method), but it allows you to see all the hoops that need to be jumped through.
const CANCEL_DELAY = 100;
let render = 0;
class Test extends React.Component {
constructor() {
super();
this.state = {
feedback: "Browse for a file...",
filename: "",
timeOut: 0
};
this.dialogOpened = this.dialogOpened.bind(this);
this.dialogClosed = this.dialogClosed.bind(this);
this.fileSelected = this.fileSelected.bind(this);
this.cancelDialog = this.cancelDialog.bind(this);
}
dialogOpened() {
console.log("Dialog opened");
window.addEventListener('focus', this.dialogClosed, { once: true});
this.setState(() => ({ filename: "" }));
}
dialogClosed() {
console.log("Dialog closed");
console.time("closed_after");
const timeOut = setTimeout(this.cancelDialog, CANCEL_DELAY);
console.log(`timeOut ${timeOut} created`);
this.setState(() => ({
feedback: 'Dialog closed',
timeOut
}));
}
fileSelected() {
console.timeEnd("closed_after"); // <25 ms on my computer
const file = event.target.files[0];
this.setState(() => ({
feedback: `File was selected`,
filename: file.name
}));
// TODO: Do something with the selected file
console.log(`+++ File: ${file.name} selected +++`);
this.destroyTimeout();
}
cancelDialog() {
console.log(`cancelDialog triggered after ${CANCEL_DELAY} ms`);
console.timeEnd("closed_after");
this.setState(() => ({ feedback: "Dialog was cancelled" }));
// TODO: Do something about the cancellation
console.log("--- File dialog was cancelled ---");
this.destroyTimeout(); // > 103 ms
}
destroyTimeout() {
console.log(`timeout ${this.state.timeOut} destroyed (${this.state.feedback})
`);
clearTimeout(this.state.timeOut);
this.setState(() => ({ timeOut: 0 }));
}
render() {
return (
<div>
<p>{this.state.feedback}</p>
<input type="file"
onClick={this.dialogOpened}
onChange={this.fileSelected}
/>
<p>{this.state.filename}</p>
</div>
);
}
componentDidUpdate() {
// // Uncomment for more feedback
// console.log(`Update ${++render}) updated state:`, this.state);
}
}
ReactDOM.render(<Test/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app' />
Upvotes: 1
Reputation: 3724
A simple way is to verify your files array if empty or not.
The cancel event means that the user has not select any file which means :
event.target.files.length = 0
const onFileChange = (e) => {
if(e.target.files.length ===0) {
setFile(null); // undefined take place instead by default
// the cancel event logics will always land here
}
else{
setResume(e.target.files[0]);
}
}
Upvotes: -1
Reputation: 2813
Well, this is probably what I would do but I don't really like it, also I think that the whole idea of performing something because the user decided not to upload a file isn't a good user experience so use it wisely.
fileInputClicked(event){
let file = event.target.files[0];
console.log("File is Selected", file);
window.removeEventListener('focus', this.handleFocusBack);
}
handleFocusBack(){
console.log('focus-back');
window.removeEventListener('focus', this.handleFocusBack);
}
clickedFileInput(){
window.addEventListener('focus', this.handleFocusBack);
}
render() {
return (
<div>
<div>
<p>Hello world</p>
<input onClick={this.clickedFileInput} type="file" onChange={this.fileInputClicked}/>
</div>
</div>
);
}
This way, when the user decides to close the file input, he focuses back on the window and you get the handleFocusBack
functionality you want and when he adds a file he goes to the fileInputClicked
.
Afaik, there is no 'out of the box' mechanism to achieve what you want.
Upvotes: 9