file input ref triggering file-picker twice - javascript

File input ref is triggering twice when I click button multiple times very quickly -
<input
type="file"
ref={uploadRef}
...
/>
<DefaultButton
onClick={onClick}
...
/>
and,
const onClick = React.useCallback(
(e) => {
uploadRef.current?.click();
// e.stopPropagation();
}
},
[]
Related info - I tried e.stopPropagation() mentioned here - https://github.com/reactjs/react-modal/issues/494. This doesn't work.
The Button onclick multiple times "quickly" loads two file pickers in sequence. If I close first, immediately I see next one and the last selection persists.

You can try to have timeout for that click event
let timer //initialise timer somewhere
const onClick = React.useCallback(
() => {
//remove the previous timer, once an user clicks again
timer && clearTimeout(timer)
timer = setTimeout(() => {
uploadRef.current?.click();
}, 200) // this is an idle time before opening file picker, it works for me, you can modify it as you wish
},
[timer])

Related

Why is my function to change the innerText of a targeted button changing the innerText of all the buttons?

This is my first JS project.
I am creating a study scheduler where the user inputs a to-do item and assigns it to a day.
JS creates the necessary elements (input field, trash button, completed button, start time button and end time button). I'm having a problem with the time picker.
I wrote a function that unhides a clock (that I wrote in html) when the user clicks on a startButton (that is created in JS). There can be unlimited startButtons so I wrote an eventListener for the array of these buttons.
The user picks the hour, minutes, and am/pm, which all get stored in variables, and then when they click the setTimeButton, the targeted startButton.innerText should update to show this time. The first time I run the function it works. After that, it is changing the targeted startButton plus every previous startTime button that I have clicked.
Here is the code:
document.addEventListener('DOMContentLoaded', () =>
Array.from(startButton).forEach(element =>
element.addEventListener('click', showTimePicker)
) );
function showTimePicker(e) {
let startOrEndTimeButton = e.target
// show clock
Array.from(time).forEach(element => element.classList.toggle('hide'))
// click setTimeButton to insert time into targeted start time or end time button
setTimeButton.addEventListener('click', function() {
startOrEndTimeButton.innerText = chosenHour.innerText
+ ':' + chosenMinutes.innerText
+ " " + chosenAmPm.innerText;
startOrEndTimeButton.style.backgroundColor = '#39ff14';
startOrEndTimeButton.style.color = 'white';
Array.from(time).forEach(element => element.classList.toggle('hide'));
})
}
Here's a simplified version of your app's logic and the problematic behaviour that you describe:
Clicking on one of the buttons and then clicking the Set button, will change the value of that particular button
Clicking on another button and then clicking on the Set button will change the value of the selected button and also the value of the previously selected button.
const setTimeButton = document.getElementById("set");
const startButton = document.querySelectorAll("button.st");
document.addEventListener( 'DOMContentLoaded', () =>
Array.from( startButton )
.forEach( element => element.addEventListener( 'click', showTimePicker ) ) );
function showTimePicker( e ) {
let startOrEndTimeButton = e.target
setTimeButton.addEventListener( 'click', function () {
console.log("event handler changing:", startOrEndTimeButton);
startOrEndTimeButton.innerText = (Math.random() * 1000).toFixed();
})
}
<button class="st one">Start 1</button>
<button class="st two">Start 2</button>
<button class="st three">Start 3</button>
<button id="set">Set</button>
Here are the changes you should make, in order for the app to work as expected:
Define the startOrEndTimeButton variable outside the showTimePicker.
Move the setTimeoutButton outside the showTimePicker.
As Mister Jojo correctly pointed out, you do not want to set an Event Listener on the setTimeoutButton every time you click on a startButton.
You can Run the code snippets in these two examples and check the resulting behavior yourself. Study the code carefully in order to understand why the suggested changes affect the behavior of the program and leave a comment in case you have any question about the suggested changes.
const setTimeButton = document.getElementById("set");
const startButton = document.querySelectorAll("button.st");
document.addEventListener( 'DOMContentLoaded', () =>
Array.from( startButton )
.forEach( element => element.addEventListener( 'click', showTimePicker ) ) );
let startOrEndTimeButton;
function showTimePicker( e ) {
startOrEndTimeButton = e.target
}
setTimeButton.addEventListener( 'click', function () {
startOrEndTimeButton.innerText = (Math.random() * 1000).toFixed();
})
<button class="st">Start 1</button>
<button class="st">Start 2</button>
<button class="st">Start 3</button>
<button id="set">Set</button>

React - onClick setTimeout/clear old timeout if there is one

I'm doing a toy project where I generate a lot of tints and shades of colors and clicking on them copies their hex values to the clipboard. Originally, I was using an onClick handler on the color article to set my state value alert to true (which would display "copied to clipboard") and using useEffect to set a 3 second timer with setTimeout before turning alert false so that the text would disappear after awhile.
The relevant code inside my Color component:
useEffect(() => {
const timeout = setTimeout(() => {
setAlert(false);
}, 3000);
return () => clearTimeout(timeout);
}, [alert]);
and
return (
<article
className={`color ${index > (list.length - 1) / 2 ? "color-light" : ""}`}
style={{ backgroundColor: `rgb(${rgbString})` }}
onClick={() => {
setAlert(true);
navigator.clipboard.writeText(hexValue);
}}
>
<p className="percent-value">{weight}%</p>
<p className="color-value">{rgbToHex(...rgb)}</p>
{alert && <p className="alert">copied to clipboard</p>}
</article>
);
This works, but in the case of multiple clicks on the same color article, I want to have the "copied to clipboard" alert disappear 3 seconds after the last click. In other words, I want multiple clicks to refresh the timer. Currently, it disappears 3 seconds after the first click, so subsequent clicks do not refresh the timer. This is because subsequent clicks on the color don't change the alert state value, so I can't refresh my timer in useEffect. I tried to solve this by calling setAlert(false) before setAlert(true) in my onClick to force the state to change every click and let me enter useEffect by force but this doesn't work. I've also tried different ways of removing useEffect entirely and trying to do everything in the onClick block since it's semantically closer to what I'm trying to do, but I can't figure out how to achieve my result that way. Any pointers?
What you can do is declare a variable alertTimeout outside of your component:
let alertTimeout;
function MyComponent(props) {
...
}
And in your component you define a function for the timeout triggering/refreshing :
function triggerAlert() {
if (alertTimeout) {
clearTimeout(alertTimeout);
}
setAlert(true);
alertTimeout = setTimeout(() => {
setAlert(false);
}, 3000);
}
And you call this function with onClick:
onClick={() => {
triggerAlert();
navigator.clipboard.writeText(hexValue);
}}
And you need to add a uesEffect to clear the timeout on component unmount:
useEffect(() => {
return function cleaning() {
if (alertTimeout) {
clearTimeout(alertTimeout);
}
}
}, [])

React : printing all past state when updating state

I'm curioused about the work of this code.
It's a simple code, I intended to raise counter for 1 and print on console.
but when I click the button the counter increases, also printing from 0 ~ to all the number that I increased.
run screenshot
Could you explain why this happens?
import { useState } from "react";
function App() {
const [counter, setCounter] = useState(0);
const onClick = () => {
window.addEventListener("click", () => {
console.log(counter);
});
setCounter((counter) => counter + 1);
};
return (
<div className="App">
<button onClick={() => onClick()}>Add & Print!</button>
<div>{counter}</div>
</div>
);
}
export default App;
You added onClick event to button tag and when the function is executed, onClick event to window is added. So whenever clicking button tag, addEventListener that order to add shows console.log is made. On the code, showing console.log doesn't need addEventListener because button tag already has onClick event.
In <button onClick={() => onClick()}>Add & Print!</button> tag, onClick event is executed whenever the tag is clicked,
Below,
window.addEventListener("click", () => {
console.log(counter);
});
addEventListener adds event.
So whenever clicking button tag, addEventListener is executed(that adds events).
import { useState } from "react";
function App() {
const [counter, setCounter] = useState(0);
const onClick = () => {
// doesn't need window.addEventListener
// window.addEventListener("click", () => {
console.log(counter);
// });
setCounter((counter) => counter + 1);
};
return (
<div className="App">
<button onClick={() => onClick()}>Add & Print!</button>
<div>{counter}</div>
</div>
);
}
export default App;
On every click you are adding an event listener. So on first click there is one event listener, on second 2, on third three and so on. (on nth click, n event listeners are there on window object).
There is also the situation of the event listener using stale state. The window event listener has closed over(closures) the old value of state and is logging it. So the first event listener is always using count = 0. The third event listener is using count = 2. So, on.

Search typehead trigger time in jquery

Is there any way how to trigger the event in every 3 seconds in laravel vuejs, but I've been using Jquery in my script.The problem is when I search and input any string, since I've been using keyup on input type it will trigger the event every characters I've type. All I want, It will trigger when you stop typing for at least 3 seconds, else if you continued typing it will not trigger the event, is there any way to achieve this? it will very helpful for me.
Currently I've been tried settimeout but every time I've type will get multiple results after setting the time.
<input type="text" class="form-control required-field" name="driver_fullname" placeholder="Enter fullname" v-model="formFields.fullname" v-on:keyup="typehead"/>
script
typehead(){
let vm = this;
let driveInfo = vm.formFields.fullname;
setTimeout(() => {
axios.put(BASE_URL + '/transportation/driver/autoComplete/' + driveInfo).then(response => {
console.log(response.data);
});
}, 500);
},
Sounds like what you really want is to debounce the user input to prevent an API call on every keystroke.
Check out https://www.npmjs.com/package/debounce
If you really want to delay by three seconds after the user stops typing, one way would be to use clearTimeout().
Add typeaheadTimeout: null to your component data.
Then:
typehead() {
let driveInfo = this.formFields.fullname;
if (this.typeaheadTimeout) {
clearTimeout(this.typeaheadTimeout);
}
this.typeaheadTimeout = setTimeout(() => {
axios.put(BASE_URL + '/transportation/driver/autoComplete/' + driveInfo).then(response => {
console.log(response.data);
});
}, 3000);
},

How to set button unvisible when timer count === 0 in React.js?

I am new in React.js and I have a question.
I want to send a feedback by button click. It is sending via axios request with timeout 3 seconds.
I put Cancel button if user wants to cancel sending feedback for this timeout (3 seconds) - (and axios request is being cancelled as well).
Then, I put timer on button, but it is situated under text even I use <span>. I attached code to codesandbox. Now it is test mode, just need to fix:
time above text - it needs to be in left of the text.
cancel function is also received wrongly as a props because of console messages (it doesn't cancel parent's state).
I need Cancel button to be show to 0 count, so I need to unmount button when it is 0 and hide it.
Any help will be appreciated. Thanks!
You have two make two changes here,
1) In send.js file make changes in following under sendApi() method,
setTimeout(() => {
this.setState({
isLoading: false
});
console.log("Ok");
}, 4000);
As the cancel button is populated only when isLoading is true, you can make it to false inside sendApi() method after the setTimeout to remove it after the given time.
2) To make the timer count and cancel text to stay in line of the button,
Under timer.js change the return statement under render() method like,
return (
<React.Fragment>{count}</React.Fragment>
);
Forked sandbox
doIntervalChange = () => {
if (this.state.count === 0) {
this.hide();
}
this.myInterval = setInterval(() => {
this.setState(
prevState => ({
count: prevState.count - 1
}),
() => {
if (this.state.count === 0) this.hide();
}
);
}, 1000);
};
doIntervalChange just run 1 time when you place it in comp did mount , try this code :D

Categories

Resources