Incorrect focusout trigger when changing focus within container - javascript

Suppose I've got the Code structure below. If I focus an element within my red container (one of the inputs), the focusin event triggers. Likewise, if I click outside the red container, the focusout event triggers.
However, if I click one of the input elements, then directly the other, both a focusout and a focusin event get triggered in quick succession.
Is there an easy way to avoid this or find out whether the second focusout event can be ignored because focus in fact stays within the relevant element, aside from ugly solutions like setting a flag on the first focusout event and waiting for a render tick to see whether another focusin event happens?
document.getElementById("el").addEventListener("focusin",
() => document.getElementById("out").innerHTML += "focusin<br/>");
document.getElementById("el").addEventListener("focusout",
() => document.getElementById("out").innerHTML += "focusout<br/>");
<div id="el" style="background-color: red; padding: 4px">
<input />
<input />
</div>
<div id="out">
</div>

As there doesn't seem to be an easy solution, I've wrote the ugly solution of waiting for the next render tick. I wrote it as reusable React hook, so if it helps someone, here it is.
export const useFocusWithin = (
element: HTMLElement | undefined,
onFocusIn?: () => void,
onFocusOut?: () => void,
) => {
const [focusWithin, setFocusWithin] = useState(false);
const isLoosingFocusFlag = useRef(false);
useHtmlElementEventListener(element, 'focusin', () => {
setFocusWithin(true);
onFocusIn?.();
if (isLoosingFocusFlag.current) {
isLoosingFocusFlag.current = false;
}
});
useHtmlElementEventListener(element, 'focusout', (e) => {
isLoosingFocusFlag.current = true;
setTimeout(() => {
if (isLoosingFocusFlag.current) {
onFocusOut?.();
isLoosingFocusFlag.current = false;
setFocusWithin(false);
}
});
});
return focusWithin;
};
export const useHtmlElementEventListener = <K extends keyof HTMLElementEventMap>(
element: HTMLElement | undefined, type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any) => {
useEffect(() => {
if (element) {
element.addEventListener(type, listener as any);
return () => element.removeEventListener(type, listener as any);
}
}, [element, listener, type]);
};

Related

How can I get focused element and change state in React component?

My intention is to update the 'isEditorFocused' state whenever the focused element changed, and if the div contains the focused element, deliver true into the Editor component.
However, the code does not work as my intention... It updates state only the first two times.
This is my Code. Actually not the exact code, but it is the core part of my question. If there is any typo, please ignore it. I checked it all in my real code file.
export default AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
const editorRef = React.useRef(null);
React.useEffect(() => {
if(editorRef.current !== null) {
if(editorRef.current.contains(document.activeElement)
setIsEditorFocused(true);
else
setIsEditorFocused(false);
}
}, [document.activeElement]}
return (
<div ref={editorRef} tabIndex="0">
<Editor isEditorFocused={isEditorFocused}></Editor>
<FileUploader {some props}/>
</div>
)
}
Not to completely change your code, but couldn't you just use onFocus and onBlur handlers?
For example:
const AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
return (
<div
onFocus={() => {
setIsEditorFocused(true);
}}
onBlur={() => {
setIsEditorFocused(false);
}}
tabIndex="0"
>
<Editor isEditorFocused={isEditorFocused}></Editor>
</div>
);
};
Working codepen
As T J mentions so eloquently, your issue is with document.activeElement
Note regarding React's current support for onFocus vs onFocusIn:
React uses onFocus and onBlur instead of onFocusIn and onFocusOut. All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.
Source: React Github
The main problem is this: [document.activeElement].
The useEffect dependency array only works with React state, and document.activeElement is not React state.
You can try using a focusin event listener on your <div>, if it receives the event it means itself or something inside it got focus, since focusin event bubbles as long as nothing inside is explicitly preventing propagation of this event.
try this way.
const AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
const handleBlur = (e) => {
setIsEditorFocused(false)
};
handleFocus = (){
const currentTarget = e.currentTarget;
if (!currentTarget.contains(document.activeElement)) {
setIsEditorFocused(true);
}
}
return (
<div onBlur={handleBlur} onFocus={handleFocus}>
<Editor isEditorFocused={isEditorFocused}></Editor>
</div>
);
};

onKeyUp / onKeyDown function when spacebar is clicked

I'm trying to action a function when either a button is clicked or the spacebar is pressed.
I have this working to some degree but it's not how I want it.
Here's my function:
const showText = () => {
const x = ToughSpellings.toString();
console.log(x);
}
Here's my button and spacebar actions:
<input type="text" id="one" onKeyUp={showText} />
<button onClick={showText}>Show Next Letter</button>
I cannot work out how to use onKeyUp without an input field. How can I use this function when the user is simply looking at the website?
Without using an input field, you'd need to setup a document event listener to listen for keyboard events.
You could have the following code in your React component:
const keyDownHandler = (event) => {
console.log(event.keyCode); // Key UP would return `38`
// Handle key event here based on `event.keyCode`
};
useEffect(() => {
document.addEventListener("keydown", keyDownHandler);
return () => document.removeEventListener("keydown", keyDownHandler);
}, []);

Ag-grid opposite method to cellFocused

folks!
Does anyone know the opposite method to cellFocused in ag-grid?
I need to detect when the focused cell loses its focus and run some actions.
Thanks for your responses.
I've found a way to support onBlur event. Since ag-grid doesn't have a built-in method, I created wy own event listener to the focus cell node and remove it after losing the focus state.
So, my code looks like this. Inside the react class I have 3 additional methods:
removeCellBlurListener = () => {
const target = document.activeElement;
if (target) {
target.removeEventListener('blur', this.onCellBlur);
}
};
addCellBlurListener = () => {
const target = document.activeElement;
if (target) {
target.addEventListener('blur', this.onCellBlur);
}
};
onCellBlur = () => {
...do something on blur
};
render () {
return (
<AgGridReact
{...restProps}
onCellFocused={(e) => this.addCellBlurListener()}
onGridReady={this.onGridReady}
/>
);
}

React document.addEventListener is detecting multiple keypresses

I am trying to create a keypress listener for my React Js calculator app and when I add the event listener in, it detects additional key presses the more I press. Is there a better place to put the event listener? When I press 1234, I get
122333344444444
/****************Button Component*************/
class CalcApp extends React.Component {
state = {
value: null,
displayNumbers: '0',
selectedNumbers: [],
calculating: false,
operator:null
};
selectMath = (selectedMath) =>{
const {displayNumbers, operator,value} = this.state;
const nextValue = parseFloat(displayNumbers)
console.log(selectedMath);
/**do math and other methods*/
render() {
document.addEventListener('keydown', (event) => {
const keyName = event.key;
if(/^\d+$/.test(keyName)){
this.selectButton(keyName)
console.log(keyName);
}
});
return (
<div>
<Display displayNumbers={this.state.displayNumbers}
selectedNumbers={this.state.selectedNumbers}/>
<Button selectedNumbers={this.state.selectedNumbers}
selectButton ={this.selectButton}
selectC = {this.selectC}
displayNumbers={this.state.displayNumbers}
selectDot = {this.selectDot}
selectMath = {this.selectMath}/>
</div>
);
}
}
let domContainer = document.querySelector('#app');
ReactDOM.render(<CalcApp />, domContainer);
Remove document.addEventListener listener from render().
The method is being called whenever the components needs to re-render (changes of state / props) which attaches yet another event listener.
Suggestion: Move document.addEventListener to componentDidMount() - executed only once, and remove it via document.removeEventListener on componentWillUnmount to prevent memory leaks.

how can i handle event capturing in react component?

Please read code first.
After css processing, it seems like memo application's single memo paper.
The goal of the component is to print a 1 when clicked(in real, the goal is to hadding redux store's state).
When i click outside of div component, it works very well. ( it printed '1' )
but when i clicked inner div component(title, date,content), onClick event also proceed ( it printed '')
how can i prevent non-valued print?
My code :
class container extends Component {
handleState = (event) => {
console.log(event.target.id)
}
render(){
return(
<div onClick={handleState} id={value}>
<div>title</div>
<div>date</div>
<div>content</div>
</div>
)
}
}
container.defaultprops = {
value: 1
}
thanks.
You can use currentTarget:
handleState = (event) => {
console.log(event.currentTarget.id)
}
About difference between target and currentTarget:
https://stackoverflow.com/a/10086501/5709697
You can use currentTarget to check if it's the target since you bound the handler to the parent e.g.
handleState = (event) = > {
if (event.target == event.currentTarget) {
console.log(event.target.id)
}
}

Categories