React handling outside clicks to close custom button menu component - javascript

I have a button menu component I've created that acts as a simple "action" menu to use in table on a per row basis. I'm having an issue handling outside clicks to close the menu when it is visible. I currently have a listener that gets attached when the button is clicked and it works fine when only a single button-menu component is being rendered. However when I have multiple being rendered (like in a table), they all react to the same events - ie. the user clicks outside and they all open/close together at the same time. How can I make it so that they all respond individually?
Sample code of what I have below:
export default class MenuButton extends React.Component {
constructor(props) {
super(props);
this.node = React.createRef();
this.state = {
showMenu: false,
}
}
handleOutsideClick = (e) => {
// Ignore clicks on the component itself
if (this.node.current.contains(e.target)) {
return;
}
this.handleButtonClick();
}
handleButtonClick = () => {
if (!this.state.show) {
// Attach/remove event handler depending on state of component
document.addEventListener('click', this.handleOutsideClick, false);
} else {
document.removeEventListener('click', this.handleOutsideClick, false);
}
this.setState({
showMenu: !this.state.showMenu,
});
}
render() {
return (
<div>
<Button
text="Actions"
onClick={this.handleButtonClick}
ref={this.node}
menuTrigger
/>
<Menu anchor={this.node.current} visible={this.state.showMenu}>
<Menu.Group title={this.props.groupTitle}>
{this.props.children}
</Menu.Group>
</Menu>
</div>
)
}
}

So my problem was due to a typo in my code that I noticed. I have a showMenu state, but in my handleButtonClick I was checking this.state.show instead of this.state.showMenu.

Related

define a button ("Cancel") in React to close the whole react-app but not the Window?

i am trying to define a button ("Cancel") in React to close the whole react-app but not the Window?
<Button onClick={() => window.close()} className={buttonClassses.join(' ')} size="large" variant="contained" color="secondary"> {props.cancel}
What should i do instead of onClick={() => window.close()}?
This will close the tab of the browser but not the whole browser
window.open("about:blank", "_self"); window.close();
Hope it helps
Well yes its possible you can call this conditional rendering in which you need to do this on handle state.
in my code you can press esc button on which im handling a state which in App.js will render UI on the conditional base
please check the below code and for demo click here
After run demo click ESC button which toggle both UI
we know our react app start from index.js or app.js so we can close all the component in App.js and instead of that we can render some UI.
import React, { Component } from 'react';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
closeMyApp: true,
};
}
escFunction = (event) => {
if (event.keyCode === 27) {
this.setState({ closeMyApp: !this.state.closeMyApp });
}
};
componentDidMount() {
document.addEventListener('keydown', this.escFunction, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.escFunction, false);
}
render() {
if (this.state.closeMyApp) {
return (
<div>
<h1>All App Componenet in this section</h1>
<p>App is runing!</p>
</div>
);
} else {
return (
<>
<h1>App is closed</h1>
<h3>You can set some UI here like</h3>
</>
);
}
}
}

Bind Mousetrap Hotkey to certain DOM element - trigger only when div is active?

I want to bind some hotkeys to a div: Whenever the user clicks somewhere inside the div and then presses the S key I want to console.log something. However, I don't want this hotkey to be global and to be triggered each and every time the user presses S.
Here is what I've got so far:
import React from "react"
import Mousetrap from "mousetrap"
export default class Mouse extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
let form = document.querySelector("form")
let m = new Mousetrap(form)
m.bind("s", () => {
console.log("s")
})
}
componentWillUnmount() {
// m.unbind("s", () => {
// console.log("s")
// })
}
render() {
return (
<form
style={{ width: "300px", height: "300px", backgroundColor: "pink" }}
>
<input type="text" />
</form>
)
}
}
The Mousetrap docs say that I can bind my mousetrap like so:
var form = document.querySelector('form');
var mousetrap = new Mousetrap(form);
mousetrap.bind('mod+s', _handleSave);
mousetrap.bind('mod+z', _handleUndo);
As you can see in my example above, that's what I've done. It does work in this sense: whenever I type S while I'm in the input of the form, the console.log is being triggered. However, I don't want to use a form and neither do I want my user to be inside an input: I just want my user to have clicked on the div. I cannot get this to work though. I would expect it to look something like this:
import React from "react"
import Mousetrap from "mousetrap"
export default class Mouse extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
let form = document.querySelector(".trigger")
let m = new Mousetrap(form)
m.bind("s", () => {
console.log("s")
})
}
componentWillUnmount() {
// m.unbind("s", () => {
// console.log("s")
// })
}
render() {
return (
<div
className="trigger"
style={{ width: "300px", height: "300px", backgroundColor: "pink" }}
>
Click me!
</div>
)
}
}
However, this doesn't work. Nothing is being triggered.
Edit: Also, one thing I don't quite understand in the first example above is that I am binding Mousetrap to form. However, the s hotkey is only ever triggered when I am inside the input field of form, but never when I just click on the form but not the input.
The reason this happens is that the Mousetrap is checking if the element is focused. divs (or in fact any other block element like a form) can only be focused if they have a tabindex defined. Inputs can be focused without that.
But I believe you do not need to explicitly bind the Mousetrap to the div at all. All you need to do is to track the active state of your div and bind() or unbind() the trap accordingly.
Example:
class Mouse extends Component {
state = {
active: false,
}
constructor(props) {
super(props);
this.trap = new Mousetrap();
}
handleClick = () => {
this.setState(({active}) => {
if (!active) {
this.trap.bind('s', this.handleKeyPress);
} else {
this.trap.unbind('s');
}
return {active: !active}
})
}
handleKeyPress = () => {
console.log('User pressed S.')
}
componentWillUnmount() {
this.trap.reset();
}
render() {
const {active} = this.state;
return (
<div
className={cN('trigger', {active})}
onClick={this.handleClick}
>
Click me!
</div>
);
}
}
Demo:
I think I've found the solution. Giving the div a tabindex makes it selectable, and thus, the hot key will be registered. I am not sure why this is, whether it's strictly necessary or a bit of a hacky solution. So all I had to do is:
<div className="trigger" tabindex="0">
...if anyone has a better explanation that goes in some more depth, feel free to post still, as I won't select this as the final answer for now.

React big calendar, render component when clicking on an event

I would like to render a component next to an event when it is clicked to show more information. Something like this https://i.stack.imgur.com/jOVsE.png
I can use onSelectEvent to create a modal but I have no idea how to put the modal next to the event that was just clicked. I have looked into creating custom events with a popover contained within the event text but there seems to be no way to fire the click event from with in the event and I cannot work out how to do it from onSelectEvent.
import React from 'react';
import { Button, Popover, PopoverHeader, PopoverBody } from 'reactstrap';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
popoverOpen: false
};
}
toggle() {
this.setState({
popoverOpen: !this.state.popoverOpen
});
}
render() {
return (
<div>
<Button id="Popover1" type="button">
Launch Popover
</Button>
<Popover placement="bottom" isOpen={this.state.popoverOpen} target="Popover1" toggle={this.toggle}>
<PopoverBody>/*Your date picker code goes here*/</PopoverBody>
</Popover>
</div>
);
}
}
Check for refrence (reactstrap)

React - How can i close all other tooltips?

I currently have a tooltip component provided to me from work which all works great, so I have made a Tooltip Wrapper to fulfil extra requirements.
The issue is i want to close all other instances of the tooltip when i tap on another. I have already added a click event listener for window so any instances of the tooltip are closed whenever i click else ware. The issue i'm having is that this handleWindowClick event doesn't fire when i click on another tooltip and as a result i am able to open multiple tooltips at once. When the requirement is that, whenever a tooltip is opened, the other closes so there is only ever one tooltip open at once.
import { Tooltip } from "Tooltip";
import React, { Component } from "react";
export default class TooltipWrapper extends Component {
constructor() {
super();
this.handleWindowClick = this.handleWindowClick.bind(this);
this.toggleTooltip = this.toggleTooltip.bind(this);
this.onTooltipClosed = this.onTooltipClosed.bind(this);
this.state = {
show: false
};
}
componentDidMount() {
window.addEventListener('click', this.handleWindowClick);
}
componentWillUnmount() {
window.removeEventListener('click', this.handleWindowClick);
}
toggleTooltip() {
this.setState({
show: !this.state.show
});
}
handleWindowClick(event) {
this.setState({
show: false
});
}
onTooltipClosed() {
this.setState({
show: false
});
}
render() {
return (
<Tooltip
open={this.state.show}
tip={this.props.tip}
position="bottom"
closeButton="visible"
onClose={this.onTooltipClosed}
>
<div onClick={this.toggleTooltip}>{this.props.children}</div>
</Tooltip>
);
}
}
Maybe adding click listener to body may help you, please try this:
document.body.addEventListener('click', this.onClickBody);

How to rebind a click event in react

What is the recommended/cleanest way to dynamically change an event's binding in react?
For example, if I initially have a button like this
<button type="button" onClick={this.handleFirstClick}>
Then in the handleFirstClick method
handleFirstClick() {
//do other stuff
//rebind the button, so that next time it's clicked, handleSecondClick() would be called
}
In case it's not entirely clear what I mean, here's what I'd like to do, using jQuery instead of React
$('#myButton').on('click', handleFirstClick);
function handleFirstClick() {
//other stuff
$('#myButton').off('click');
$('#myButton').on('click', handleSecondClick);
}
Solution 1: React State and Ternary Expressions
In order to change the event's binding, you'll need to have a if-else within the render method. Create some state for the component to handle whether the button has been clicked yet. After the first click, set the state so that in the future the second function will be run. You can include a basic ternary expression to check the state in your render().
class FancyButton extends React.Component {
constructor() {
super()
this.state = {
clicked: false
}
//Bindings for use in render()
this.onFirstClick = this.onFirstClick.bind(this)
this.onSecondClick = this.onSecondClick.bind(this)
}
onFirstClick() {
console.log("First click")
this.setState({
clicked: true
})
}
onSecondClick() {
console.log("Another click")
}
render() {
return ( <
button onClick = {
this.state.clicked ? this.onSecondClick : this.onFirstClick
} > Click < /button>
)
}
}
ReactDOM.render( < FancyButton / > , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Solution 2: Persisting State in an Object Property
In a more general sense, you may not need to change the event handler in your render method. If you just call one onClick handler and toggle an object property, you can skip rerendering the react component after each click (due to skipping the call to this.setState).
class FancyButton extends React.Component {
constructor() {
super()
this.clicked = false
//Bindings for use in render()
this.onClick = this.onClick.bind(this)
}
onClick() {
if (!this.clicked) {
this.onFirstClick()
} else {
this.onSecondClick()
}
this.clicked = true
}
onFirstClick() {
console.log("First click")
}
onSecondClick() {
console.log("Another click")
}
render() {
return ( <
button onClick = {
this.onClick
} > Click < /button>
)
}
}
ReactDOM.render( < FancyButton / > , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I would personally recommend the second solution, as it is more efficient, but you can decide on your own which fits your situation the best.
Just keep a counter of how many times button is clicked, and then use it to assign a handler. See this snippet,
class App extends React.Component{
constructor(props){
super(props)
this.state={
clicked: 0
}
}
firstClick(){
console.log("first click")
this.setState({clicked: 1})
}
afterClick(){
console.log("more clicks")
}
render(){
return(
<div><button onClick={this.state.clicked === 0 ? this.firstClick.bind(this) : this.afterClick.bind(this)}>Click me</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You could use a closure.
const createClickHandler = () => {
const firstClickHandler = () => {}
const secondClickHandler = () => {}
let clicked = false
return () => {
if (clicked) return secondClickHandler()
clicked = true
firstClickHandler()
}
}
Create a function that watches the state and determines which function should be ran.
handleClick() {
if (this.state.clickNumber === 1) {
this.handleFirstClick()
} elseif (this.state.clickNumber === 2) {
this.handleSecondClick()
}
}
handleFirstClick() {
//do stuff
this.setState({clickNumber: 2})
}
handleSecondClick(){
//do other stuff
// maybe increment click number again this.setState({clickNumber: 3})
}
In React, you'd be best off using state to manage these events. You can hold the state and actions in the individual button components, or in the parent component that renders the button components.
In set this up the button components utilize a binary state that toggles between click one and 2 functions with a Boolean value. This could also easily change to function two and stay there by modifying the buttons's handleClick setState call, or keep iterating to additional functions - or even call the same function with an updated input each click (depending on your use case).
In this example, I decided to show how this would work in a stripped down React App. In this example you'll notice that I keep the state in each button. It's doubtful that another component will need to track the click count, so this follows the react principal of pushing the state down to the lowest possible component. In this case, I allowed the parent component to hold the functions that would act on the button state. I didn't see a necessity in these functions and their overhead also being duplicated with each button component, but the button components certainly could have called the functions themselves as well.
I added two buttons to show how they will individually maintain their state. Forgive the comment block formatting, it's not displaying well outside of the snippet window.
/**
* #desc Sub-component that renders a button
* #returns {HTML} Button
*/
class BoundButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = ({
//toggled in this example to keep click state
click2: false,
});
}
handleClick(e) {
//calls parent method with the clicked button element and click state
this.props.click(e.nativeEvent.toElement, this.state.click2);
//toggles click state
this.setState({ click2: !this.state.click2 });
}
render() {
return (
<button
id = {this.props.id}
name = {this.props.name}
className = {this.props.className}
onClick = {this.handleClick} >
Click Here {this.state.click2 ? '2' : '1'}!
</button>
);
}
}
/**
* #desc Component that creates and binds button sub-components
* #returns {HTML} Bound buttons
*/
class BindExample extends React.Component {
constructor(props) {
super(props);
this.handleButtonClick = this.handleButtonClick.bind(this);
this.clickAction1 = this.clickAction1.bind(this);
this.clickAction2 = this.clickAction2.bind(this);
this.state = ({
//state vars
});
}
clickAction1(el) {
console.log('Action 1 on el: ', el);
}
clickAction2(el) {
console.log('Action 2 on el: ', el);
}
handleButtonClick(el, click2) {
console.log('click!');
if (click2) this.clickAction2(el);
else this.clickAction1(el);
}
render() {
return (
<div>
<BoundButton
id='ex-btn1'
name='bound-button-1'
className='bound-button'
click={this.handleButtonClick} />
<BoundButton
id='ex-btn2'
name='bound-button-2'
className='bound-button'
click={this.handleButtonClick} />
</div>
);
}
}
/**
* #desc React Class renders full page. Would have more components in a
* real app.
* #returns {HTML} full app
*/
class App extends React.Component {
render() {
return (
<div className='pg'>
<BindExample />
</div>
);
}
}
/**
* Render App to DOM
*/
/**
* #desc ReactDOM renders app to HTML root node
* #returns {DOM} full page
*/
ReactDOM.render(
<App/>, document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root">
<!-- This div's content will be managed by React. -->
</div>

Categories