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);
}
}
}, [])