How to stop onMouseOut event after onClick event (ReactJS)? - javascript

I need to make a hidden button visible when hovering over an adjacent text. This is being done through onMouseEnter and onMouseLeave events.
But when clicking on the text besides, I need to make the button completely visible and stop the onMouseLeave event.
So far what I have tried: trying to remove the onMouseLeave event using removeEventListener.
I know we could do this with the help of a variable like:
const mouseOut = (divId) => {
if (!wasClicked)
document.getElementById(divId).style.visibility = "hidden";
}
But since I have way too may different buttons and text, I do not want to use variables either.
<div className="step-buttons" onMouseEnter={mouseOver.bind(this, 'feature')}
onMouseLeave={mouseOut.bind(this, 'feature')}
onClick={showButtonOnClick.bind(this, 'feature')}>Test Suite
</div>
Is there anything that could help me here?
Any suggestion would be welome,
Thanks in advance :)

Way 1:
You can mount an eventlistener for leaving in the MouseEnter function and remove it in the onClick eventhandling with refs like so:
constructor(props) {
super(props);
this.text;
this.onMouseLeave = this.onMouseLeave.bind(this);
}
onMouseEnter(){
//apply your styles
this.text.addEventListener('mouseleave', this.onMouseLeave);
}
onMouseLeave(){
//remove your styles
}
showButtonOnClick(){
//remove the eventlistener
this.text.removeEventListener('mouseleave', this.onMouseLeave);
}
render(){
return(
<div ref={(thiscomponent) => {this.text = thiscomponent}}
onMouseEnter={this.onMouseEnter.bind(this, 'feature')}
onClick={this.showButtonOnClick.bind(this, 'feature')}>
Test Suite
</div>);
}
The ref ist just a reference for your div, so that you can mount the eventlistener to it.
You have to bind the function in the constructor, as .bind(this) will create a new function when you call it, to prevent mounting the eventhandler multiple times.
Way 2(probably better):
Another method would be to save the click on the text in the state, and conditionaly change what your onMouseLeave function does based on that:
constructor(props) {
super(props);
this.state={
clicked: false,
}
}
onMouseEnter(){
//apply your styles
}
onMouseLeave(){
if(!this.state.clicked){
//remove styles
}
}
showButtonOnClick(){
this.setState({clicked: true});
}
render(){
return(
<div
onMouseEnter={this.onMouseEnter.bind(this)}
onMouseLeave={this.onMouseLeave.bind(this)}
onClick={this.showButtonOnClick.bind(this)}>
Test Suite
</div>);
}

You can use removeEventListener to remove the mouseleave event in your showButtonOnClick function
showButtonOnClick(){
...your function...
document.getElementById('step-button').removeEventListener('mouseleave',this.callback());
}
callback(){
console.log("mouseleave event removed");
}

As you are doing this many times, it seems like creating these as components and using its state to store visibility without creating tons of global variables might be worthwhile.

Related

In stencil js, how can i check if button has been clicked in another component from a different class

I have a component called button.tsx, this components holds a function that does certain things when the button is clicked, this.saveSearch triggers the saveSearch() function.
button.tsx
{((this.test1) || this.selectedExistingId) &&
(<button class="pdp-button primary"
onClick={this.saveSearch}>{this.langSave}</button>)
}
In sentence.tsx i want to be able to see when this button is clicked and show a certain div if the user has clicked it.
sentence.tsx
{onClick={saveSearch} && (<div class="styles-before-arrow">{this.langConfirmSearchSaved}</div>)}
You have a few options:
You can attach a click event listener for the button component in sentence.tsx. Take note that this may be trickier if you are working with elements which are encapsulated in Shadow DOM:
addButtonLister(): void {
document.querySelector('.pdp-button')
.addEventListener('click'), (e) => {
// add your logic here.
});
}
You can use EventEmitter (https://stenciljs.com/docs/events#events). In your button.tsx, you can add this:
#Event({eventName: 'button-event'}) customEvent: EventEmitter;
Then add something like this on button's onClick:
emitEvent() {
customEvent.emit('clicked');
}
render () {
return <button onClick={this.emitEvent}>{this.langSave}</button>
}
then from your sentence.tsx, add an event listener to your button component:
// say your button component's tag is <button-component>
document.querySelector('button-component')
.addEventListener('button-event', (e) => {
// your logic here.
});
You can use Stencil Store, but depending on your overall use-case, I am not sure if this may be an overkill - https://stenciljs.com/docs/stencil-store#store-state

Detect component hover on scroll in React

As you may already know, mouseenter and mouseleave events are NOT triggered if the mouse doesn't move, which means that if you scroll over an element without moving the mouse, hover effects are ignored.
This answer describes the strategy to overcome this:
1: Add a scroll listener to the window.
2: In the handler, call document.elementsFromPoint.
3: Manually call the actual mouseover handler for those elements.
4: Manually call the actual mouseleave handler for elements no longer being hovered.
We also must take into account that registering a listener for each component we want to detect the hover of, is a waste of resource.
My idea is to create a singleton object and subscribe to it from each component.
I'm fairly new to react so I will try to write pseudo-react-code to describe my idea:
// A global singleton object that registers the listeners ONCE.
class Singleton {
subscribedElements = {}
subscribe(element, isHovered, setIsHovered) {
subscribedElements[element] = {
isHovered: isHovered,
setIsHovered: setIsHovered
}
}
unsubscribe(element) { ..... }
constructor() {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('mousemove', this.handleMove);
}
handleMove() {
//omitted
}
handleScroll() {
hoveredElements = document.elementsFromPoint(mousePosition)
// Iterate every element that is subscribed and check if they are in the list of elements that are hovered.
subscribedElements.map( e => {
if (hoveredElements.contains(e)) {
subscribedElements[e].setIsHovered(true)
} else {
subscribedElements[e].setIsHovered(false)
}
})
}
}
// a hook to be used in all elements that want to detect if they are hovered
function useHovered(element) {
[isHovered, setIsHovered] = useState(false);
Singleton.subscribe(element, isHovered, setIsHovered);
return isHovered;
}
// All components that want to check if they are being hovered would do this
function MyComponent(props) {
isHovered = useHovered(this);
return <div>{ isHovered ? 'hovered' : 'not hovered'</div>
}
Now, what is the most efficient and clean way to do something like this?
I have read about useContext but I'm not sure how to apply it to this solution.
I'm not sure how to create this singleton. Does it have to be a component? Can it be done in the new functional way?

binding event handlers in react boilerplate

There are 4 methods for binding event handlers that i know. The first one is by binding within the DOM element in render():
<button onClick = {this.clickHandler.bind(this)}>Event</button>
The second one is using an arrow function within the DOM element in render():
<button onClick = {() => this.clickHandler()}>Event</button>
The third one is by binding within the class constuctor:
constructor(props) {
super(props)
this.state = {
message: "Click"
}
**this.clickHandler = this.clickHandler.bind(this);**
}
render() {
return (
<div>
<div>{this.state.message}</div>
**<button onClick = {this.clickHandler}>Event</button>**
</div>
)
}
The forth way is by changing the class property to an arrow function:
clickHandler = () => {
this.setState({
message: 'Goodbye!'
})
}
So which way is best?
From what I know :-
In 1st case, a newly bound clickHandler will be created on every render of your React app.
In 2nd case, a new anonymous arrow function will be created which internally calls the clickHandler (on-execution) on every render of your React app.
Both 3rd and 4th are better since they cause one-time creation of the clickHandler. They are outside the render flow.

onClick function is running on another event listener

Have been playing around with react. Have two event listeners the input which listens onChange and the button which should push the value to the array when its clicked.
Here's the code:
import React, { Component } from 'react';
let arr = [];
class App extends Component {
constructor() {
super();
this.state = {text: 'default'}
}
update( e ) {
this.setState({text: e.target.value})
}
add ( value ) {
arr.push(value)
console.log(arr)
}
render() {
return (
<div>
<h1>{this.state.text}</h1>
<input onChange={this.update.bind(this)}/>
<button onClick={this.add(this.state.text)}>Save</button>
</div>
)
}
}
export default App
The problem that the add function is running on change. Can't really get why.
Any suggestions?
onChange() triggers update()
update() calls this.setState() which changes state.
A state change causes render() to be invoked to re-render according to new state.
Rendering <button onClick={this.add(this.state.text)}>Save</button> invokes add() every time render() runs.
In order to defer invoking add(), you can define a function which gets triggered by the click event, as was shown in another answer. Alternatively, you can achieve the same functionality by adding a class method which encapsulates the trigger functionality:
addText() {
this.add(this.state.text)
}
render() {
…
<button onClick={this.addText.bind(this)}>Save</button>
This may or may not work for you, but in the context of the example, given, this would work.
Change <button onClick={this.add(this.state.text)}>Save</button>
To <button onClick={() => this.add(this.state.text)}>Save</button>
In your variant function add firing when component is rendering, and when you call setState with onChange of input you call this re-render.
The problem is add(this.state.text) is called whenever render() is called. To avoid this, you do not need to send the state as parameter, all you need to do is
<button onClick={this.add}>Save</button
or if you want to send a parameter you should bind it
<button onClick={this.add.bind(this, this.state.text)}>Save</button>

Subclass component to override event handler?

I have a complex React component (Combobox from react-widgets), and would like to change a small part of it's behavior - I want to override the onKeyDown event. If enter is pressed, I want to handle it myself, and the Combobox shouldn't see the keypress. Apart from that, I'd like it to be a drop-in replacement.
I know React strongly recommends composition over inheritance, but in this case composing seems bad - if I make a new component that includes Combobox, I'd have to forward every prop to the combobox:
class MyCombobox extends Component {
render() {
return (<Combobox data={this.props.data}
value={this.props.value}
onChanged={this.props.onChanged}
...
/>);
}
}
I also tried just including a regular Combobox, and setting onKeyDown in its container, but that didn't override the event - the original Combobox still gets the event, and my Container gets it, too:
// in MyContainer.render:
return (
<div className="MyContainer">
<Combobox data={...} onKeyDown={this.handleKeyDown} />
</div>
);
This here almost works:
class MyCombobox extends Combobox {
handleKeyDown(event) {
console.log('MyCombobox.handleKeyDown', event);
console.log(this); // this: null!
if (event.key === 'Enter') {
event.preventDefault();
// super.close(); // fails
}
}
render() {
let result = super.render();
return React.cloneElement(result, {onKeyDown: this.handleKeyDown});
}
}
I can intercept the event and prevent the Combobox from processing it. However, this is null. If I make handleKeyPress an arrow function instead, I can access this (it is the MyCombobox object), but I can't access any of the Combobox's properties. Especially super.close() doesn't work (I get "'super' outside of function or class").
I'm not sure if it helps, but you can easily pass all the props to other component this way (using ES6 unwrapping):
class SomeComponent extends React.Component {
render() {
return(
<SomeOtherComponent {...this.props} />
)
}
}
I think it might help you to solve your problem using composition.
So, this kind of works, and it seems to be doing things The React Way (composition, only passing information to children via props).
What it does is, when I enter custom text and press enter, it takes that text and closes the menu, instead of discarding the text and selecting some element of the list.
It still doesn't behave 100% like a native combo box, so I'm giving up for now and trying a different component :-). But maybe this is a useful workaround for someone else trying to "subclass" a component.
You might be able to simplify this, I'm not sure the manual handling of value is neccessary.
class MyCombobox extends Component {
handleKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.setState({open: false, value: event.target.value});
}
}
constructor() {
super();
this.state = {open: false, value: null};
}
render() {
return (
<Combobox value={this.state.value} open={this.state.open}
onToggle={(isOpen)=>this.setState({open:isOpen})}
onChange={(newValue)=>this.setState({value:newValue})}
onKeyDown={this.handleKeyDown} {...this.props} />
);
}
}

Categories