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

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

Related

file input ref triggering file-picker twice

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])

Force a HtmlButton to lose focus

I have a VueJS app where a HTML button is on the screen and when it's clicked it's appropriate to have "focus" but I'd like the option to remove the focus after a short delay.
if (props.defocus === "true" || props.defocus === true) {
// de-focus the button after 400ms
setTimeout(() => {
if (el.value !== null) {
el.value.blur();
}
}, 400);
}
Now I did mention that this is a VueJS project but really this is just DOM API and JS/TS. I use setTimeout to wait 400ms and I am able to call the blur() function on the button. However, this does not appear to do anything. Now I guess this could be two things:
I have successfully removed the focus on the button element according to the DOM but somehow VueJS is not picking this up and reacting to it (aka, the focus styling remains)
The call to focus() is somehow silently ignored (there are no errors)
Is there a way to inspect the focus state in developer tools? Is there something I'm missing in getting the behavior I'm looking for?
Just for the visual minded in the group, the component above is where when I click on a button and make it selected the focus state is represented by a purple outline. I want that to go away after a user has clicked it.
Trying to use the pointer to the button didn't work but instead I found that simply querying for the focus element gives me a handler where I can call blur and have it work:
if (props.defocus === "true" || props.defocus === true) {
// de-focus the button after delay
setTimeout(() => {
// QUERY FOR FOCUS ELEMENT
const e2 = document.querySelector(":focus");
if (e2 && (e2 as HTMLButtonElement).blur) {
(e2 as HTMLButtonElement).blur();
}
}, Number(props.defocusDelay));
}
This works without issue and unless someone else has a better solution I will mark this as the correct one.
Make a directive file Defocus.ts;
export default {
name: 'defocus',
priority: 700,
bind(el: any, binding: any) {
el._onClick = () => {
setTimeout(()=> {
el.blur();
}, 400)
}
el.addEventListener('click', el._onClick);
},
unbind(el: any) {
el.removeEventListener('click', el._onClick);
}
};
Then you import and use
import Defocus from "#/shared/directives/Defocus";
#Component ({
directives: {
Defocus
}
})
Then in template:
<button v-defocus>Click Me</button>

onClick doesn't work on first click after rerender

const NavigationButtons = ({onBtnClicked, btnClass, label, route, btnAct}) => {
return (
<p
className={`btn ${btnClass} ${btnAct}`}
onClick={() => onBtnClicked(route)}>
{label}
</p>
);
};
This is my button component, I'm giving it to another component as btns1 props
{!isSigned?btns1:windowPixel?btns1:null}
Basically, when isSigned is false, btns1 is rendered. There's really no problem here. When isSigned is true, it checks if windowPixel is true, this is changed to true by App.js as a state by measuring the current window. It works perfectly unless I click the button. Resize the window where windowPixel will be false, then on my first click, it doesn't trigger onClick. After that onClick works again.
componentDidMount() {
if (window.matchMedia(`(max-width: 990px)`).matches) {
this.resizeWindow(true);
}
window.addEventListener("resize", () => this.resizeWindow(window.matchMedia(`(max-width: 990px)`).matches));
}
This is what checks the window size for windowPixel. chatScroll, is for the panel that expands when btn1 is clicked, btnAct is just for a css that change the color of the button while the panel is expanded. For now, I've put click(), like a bandaid.
resizeWindow = (windowPixel) => {
const {chatScroll, btn1Act} = initialState;
if (windowPixel !== this.state.windowPixel) {
if (windowPixel === false) {
if (this.state.isSigned) {
document.getElementById('btn1').click();
this.setState({chatScroll, btn1Act});
}
}
this.setState({windowPixel});
}
};
The Reason Is Simple Brother in First Click Your Object or function or variable what ever it is , Just Initialize in first click and when you click second time it will Work as per your code.

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

React trouble with event.stopPropagation()

I have two components here, the first one is a table, and I have an on-click event attached to one of the <td>'s in every row that summons a little tooltip-like window:
<td onClick={ () => loadSelectorWindow(p.product_id) }>
{
p.selectorActive &&
<SelectorWindow
cancelWindow={this.cancelSelectorWindow}
product_id={p.product_id}/>
}
</td>
The function bound to the <td> click will search through all products in state and flip a boolean on the selected product to display the tooltip.
loadSelectorWindow = (product_id) => {
this.setState({ products: this.state.products.map( p => {
if (p.product_id == product_id) {
p.variationSelectorActive = true
} else {
p.variationSelectorActive = false
}
return p
})})
}
However, the tooltip also needs a button with a window cancel event linked to it:
// within <SelectorWindow />
<p onClick={ () => {cancelWindow(event)} }> X </p>
This function cycles through state and sets all of the display booleans to false.
cancelSelectorWindow = (event) => {
event.stopPropagation()
this.setState ({ products: this.state.products.map( p => {
p.variationSelectorActive = false
return p
})})
}
Putting breakpoints in the code I can see that the cancel button is correctly calling the cancel function and setting the displayTooltip boolean to false, temporarily. The problem is, the loadSelectorWindow is ALSO getting fired when the cancelWindow button is clicked, and the boolean is set back to true DX.
This is why I attempted to put the event.stopPropagation call in there but obviously something is still calling it. There is no other place in my code that the loadSelectorWindow function is mentioned... Any ideas how I can stop it from getting called?
I forgot to pass event to the cancelWindow callback function. React why is your syntax so confusing sometimes...
Fix:
<p onClick={ (event) => {cancelWindow(event)} }> X </p>
You have one html element nested inside the other, so if you click the inner one then you will receive onClick events for both. So that is what you are getting. You need to redesign the layout of the page so that does not happen.

Categories

Resources