I have a blur event on a text input that toggles on a button (renders it with React). When you click on the button, the blur event fires on the text input which removes the button from the DOM, but the button click event is not fired. I tried wrapping the code that removes the button from the DOM in a 100ms timeout and it works but feels hacky, is there a better way?
Here's the gist of the code:
const blurHandler = e => {
setShowInput(false); //this prevents buttonHandler from being fired
// setTimeout(() => setShowInput(false), 100); // this works with 100ms, but not with 50ms for example
}
const buttonHandler = e => {
console.log('Button clicked!');
}
const focusHandler = e => {
setShowInput(true);
}
return (
<div>
<input type="text" onFocus={focusHandler} onBlur={blurHandler} >
{showInput && <button onClick={buttonHandler}>Apply to all</button>}
</div>
)
This is easy, you just need to use a different event trigger. onClick is too late, it won't fire until after the textarea blur happens, but onMouseDown will fire before it:
const btn = document.getElementById('btn');
const txt = document.getElementById('txt');
txt.onblur = () => {console.log("Textarea blur fired")}
txt.onfocus = () => {console.log("Textarea focus fired")}
btn.onclick = () => {console.log("Button onClick fired")}
btn.onmousedown = () => {console.log("Button mouseDown fired")}
btn.onmouseup = () => {console.log("Button mouseUp fired")}
<textarea id="txt">Focus me first</textarea>
<button id="btn">Click me next</button>
Since you conditionally render the button with:
{showInput && <button onClick={buttonHandler}>Apply to all</button>}
As soon as you set showInput to a falsy value, it gets removed from the DOM. You have couple of options:
Option 1 (If you don't have to remove the button from the DOM)
return (
<div>
<input type="text" onFocus={focusHandler} onBlur={blurHandler} >
<button style={{opacity: showInput ? 1 : 0}} onClick={buttonHandler}>Apply to all</button>}
</div>
)
Setting opacity 0 will hide the button, but won't remove it from the dom.
Option 2 (If you have to remove the button from the DOM)
const btnClickedRef = useRef(false)
const buttonHandler = e => {
console.log('Button clicked!');
btnClickedRef.current = true
}
return (
<div>
<input type="text" onFocus={focusHandler} onBlur={blurHandler} >
{showInput && btnClickedRef.current && <button onClick={buttonHandler}>Apply to all</button>}
</div>
)
Related
let form = document.querySelector('form#my_form')
form.addEventListener('change', event => {
console.log('Change event fired')
let target = event.target;
console.log('target')
console.log(target)
console.log(`target.value; ${target.value}`)
})
let myName = document.querySelector('input[name="my_name"]')
myName.value = "Daniel"
myName.dispatchEvent( new Event("change") )
<form id="my_form">
<input name="my_name" value="Dani"/>
<input name="my_surname" value="Pardo"/>
</form>
Why myName.dispatchEvent( new Event("change") ) is not being fired?
If I change manually in input text box, this is fired. But if I do it changing value and dispatching event programmatically this not working.
Form EventListener work fine because when text input is changed by typing and focus out, this is fired. I know I can focus in and focus out the input box but I want to know why myName.dispatchEvent( new Event("change") ) didn't work.
With my_name.value = "Daniel", the input box is changed, but event is not fired
HTML
<form id="my_form">
<input name="my_name" value="Dani"/>
<input name="my_surname" value="Pardo"/>
</form>
JS
let form = document.querySelector('form#my_form')
form.addEventListener('change', event => {
console.log('Change event fired')
let target = event.target;
console.log('target')
console.log(target)
console.log(`target.value; ${target.value}`)
})
let myName = document.querySelector('input[name="my_name"]')
myName.value = "Daniel"
myName.dispatchEvent( new Event("change") )
I tried to find the answer to this issue but I didn't find it.
I have a React form with just a text area and a submit button. I set the submit button's state to disabled to begin with, and then after the user enters 100 chars or more I want to enable the Submit button.
The issue I'm having right now is that after I input more than 100 chars, the submit button remains disabled and doesn't change to enabled state.
This is the updateFieldLength function I am calling upon the textarea field's onChange.
const updateFieldLength = e => (
setText(e.target.value), () => (
validateFieldLength()
)
)
and this is the validateFieldLength function:
function validateFieldLength() {
if (submitDisabled && text.length > 100) {
setSubmitDisabled(false);
} else if (!submitDisabled && text.length <= 100) {
setSubmitDisabled(true);
}
}
Your problem seems to be that the onchange event is triggered only when textarea loses focus. I guess it would work with the oninput event, shown below
const setBackground = (element, background) => {
element.style.background = background;
}
<textarea id="test-on-change" onchange="setBackground(this, 'green')" rows="10" cols="30">Example with onchange, start typing...</textarea>
<textarea id="test-on-input" oninput="setBackground(this, 'yellow')" rows="10" cols="30">Example with oninput, start typing...</textarea>
This should do the job
import React from 'react'
const MyComponent = () => {
const [text, setText] = useState('')
const handleTextChange = e => {
setText(e.target.value)
}
return(
<>
<textarea onChange={handleTextChange}>
{text}
</textarea>
<button disabled={text.length < 100}>
Submit
</button>
</>
)
}
at the current point, this code works, but when the user clicks to hide the menu, the useClickOutside fires too, the menu toggles off and on again... would there any way to fix that so when clicks outside it closes but when clicks the button it toggles on/off ?
const useClickOutside = (ref, handler) => {
useEffect(() => {
const clickHandler = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', clickHandler);
return () => {
document.removeEventListener('mousedown', clickHandler);
};
});
};
const Settings = () => {
const ref = useRef();
const [toggle, setToggle] = useState(false);
useClickOutside(ref, () => setToggle(false));
return (
<div className='settings'>
<button onClick={() => setToggle(!toggle)} className='settings__button'>
Menu
</button>
{toggle && (
<div ref={ref} className='settings__panel'>
<Link className='settings__links' to='/user/settings'>
Your Profile
</Link>
<Link className='settings__links' to='/user/settings'>
Todos history
</Link>
<Link className='settings__links' to='/user/settings'>
Settings
</Link>
<Link className='settings__links' value={'Logout'} to='/user/login'>
Logout
</Link>
</div>
)}
</div>
);
};
You might consider adding a onBlur event on the .settings div with a tabIndex=0.
You can then then capture blurs of the div and test if the event came from within the div or not.
const onBlur = (e: FocusEvent < HTMLElement > ) => {
if (opened?) {
const element = e.relatedTarget;
if (element == null) {
// dropdown was blured because window lost focused. probably close.
} else if (element != e.currentTarget) {
if (!e.currentTarget.contains(element as Node)) {
// blured element is not in .settings. close
}
}
}
};
If you want to get fancy you can also add a keydown and close on escape.
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// close!
}
);
Here is a code sandbox that implements these items.
You could make use of event stopPropagation. Add the call event.stopPropagation() to your onClick handler function that hides the menu.
<button
onClick={(e) => {
e.stopPropagation();
setToggle(!toggle);
}}
className='settings__button'
>
Menu
</button>
This will prevent that the onClick event bubbles upwards to the next event listener which would be your onClickOutside listener.
UPDATE:
This will only work if your event listener is listening for onClick events. Your inline onClick event listener will stop the propagation of the event of type click only.
document.addEventListener('click', clickHandler);
return () => {
document.removeEventListener('click', clickHandler);
};
My field onClick event toggles a dropdown, the onFocus event opens it.
When the onFocus event is fired the onClick event is fired afterwards and closes the newly opened dropdown.
How can I prevent firing on Click in Case onFocus fired?
preventDefault and stopPropagation do not work, both events are always fired
<TextInputV2
label={label}
onChange={handleInputOnChange}
onClick={handleOnClick}
onFocus={handleOnFocus}
onKeyUp={handleInputOnKeyUp}
readOnly={!searchable}
value={inputValue}
/>
.......
const handleOnFocus = (event: React.FocusEvent): void => {
if (!isOpen) {
changeIsOpen(true)
}
}
const handleOnClick = (event: React.SyntheticEvent): void => {
if (!searchable) {
toggleOpen()
}
}
You will want to change onClick to onMouseDown. Since event order is
mousedown
change (on focused input)
blur (on focused element)
focus
mouseup
click
dblclick
from: this answer
You want to preventDefault/stoPropagation BEFORE the focus event, which means you have to use "onMouseDown" to properly stop it before the focus event get triggered.
In your case it would be:
<TextInputV2
label={label}
onChange={handleInputOnChange}
onMouseDown={handleOnClick}
onFocus={handleOnFocus}
onKeyUp={handleInputOnKeyUp}
readOnly={!searchable}
value={inputValue}
/>
const handleOnClick = (event) => {
event.preventDefault()
event.stopPropagation()
if (!searchable) {
toggleOpen()
}
}
https://jsfiddle.net/reactjs/69z2wepo/
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'hello'
}
this.lastFocus = 0;
}
handleOnClick(ev) {
const now = new Date().getTime();
console.log('diff since lastFocus');
console.log(now - this.lastFocus);
if (now - this.lastFocus < 200) {
return;
}
const newText = this.state.text + 'c';
this.setState({text:newText})
}
handleOnFocus(ev) {
this.lastFocus = new Date().getTime();
const newText = this.state.text + 'f';
this.setState({text:newText});
}
render() {
return <div>
<input name="" id="" cols="30" rows="10"
value={this.state.text}
onClick={this.handleOnClick.bind(this)}
onFocus={this.handleOnFocus.bind(this)}
></input>
</div>;
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
You store the time of your lastFocus -- not in this.state because that updates asynchronously and you cannot rely on that being updated in the onClick handler by calling setState in the onFocus handler. You put it directly on the instance and update it directly.
You can just use a rule of thumb that says if the last focus was within 200ms, then this onClick handler is from the same event as the onFocus handler, and therefore not run the rest of your onClick handler.
My fiddle is not obviously your entire use case, I'm just adding f on focus and c on click to the input text.
I had 6 circles. I'm trying to prevent my users to click NOT in order from 1-6, from left-right.
Ex : Users should not allow clicking on the 2nd or 3rd one if they not yet clicked on the first one.
I have access to the index of all those circles.
if(steps.indexOf(selector) != 0){
alert("Please start by selecting your device.");
return false;
}
Since my circles click bind using a one() so it only listening to an event one time only, after they click on a wrong order, my alert pop up, BUT when I click on the same circle again, nothing will happen anymore since the circle bind using one() function.
Is there a way to reset the one() on a $(this) object so that way it will listen to an event again?
$('.' + selector).on("click", function() {
...
});
How reset the one() function on an object - jQuery ?
Don't add a one time listener, instead add an all time listener, and solve your problem using business logic. For example like this example:
const $first = document.getElementById('first'),
$second = document.getElementById('second'),
$third = document.getElementById('third');
function makeOrderedClick() {
let currentStep = 0;
return index => {
if (index !== currentStep) {
alert("Please click step" + currentStep);
return;
}
currentStep++;
};
}
const orderedClick = makeOrderedClick();
$first.addEventListener('click', e => {
orderedClick(0);
})
$second.addEventListener('click', e => {
orderedClick(1);
})
$third.addEventListener('click', e => {
orderedClick(2);
})
<div id="app">
<button id="first">
First
</button>
<button id="second">
Second
</button>
<button id="third">
Third
</button>
</div>
// index of the button that is supposed to be clicked
var curIdx = 0;
// get all the buttons present in the page
const $buttons = document.getElementsByTagName('button');
// iterate all the buttons
for (let idx = 0; idx < $buttons.length; idx++)
{
// add Event Listener to each button
$buttons[idx].addEventListener('click', e => {
// is the index of the button the same as the control var?
if (idx === curIdx) {
curIdx++;
console.log('correct index');
}
else
{
alert('not the correct index');
}
});
}
<div id="app">
<button id="first">
First
</button>
<button id="second">
Second
</button>
<button id="third">
Third
</button>
</div>