I have created a dropdown menu for my web app and when clicking on a icon i'm opening my dropdown menu. I want to remove the dropdown menu when i click anywhere other than the dropdown menu. My current approach removes the element when clicking outside the element. But i cannot open the dropdown menu when clicking on the icon after that. How can i fix it?
class DropDown extends Component {
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
this.state = {
openmenu:false
}
};
myFunction(e) {
e.stopPropagation();
this.setState({
openmenu: !this.state.openmenu
})
render() {
window.onclick = function (event) {
if (!event.target.matches('myDropdown')) {
document.getElementById('myDropdown2').remove();
}
}
return (
<div className="dropdown small_font" id="myDropdown" >
<i className="fa fa-cog user_settings_icon" style={{marginTop: '6px'}} onClick={this.myFunction}
aria-hidden="true"></i>
{this.state.openmenu?
<div className="dropdown-content" id="myDropdown2">
<a className="dropdown_item"><i className="fa fa-trash-o margin_right10px" aria-hidden="true"></i>Delete</a>
<a className="dropdown_item"><i className="fa fa-flag-o margin_right10px" aria-hidden="true"></i>Report</a>
</div>:null
}
</div>
);
}
}
The error i'm getting when clicking on the icon for the second time
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
If you don't want to keep track of adding and removing click events on unmount and a solution that works across all browsers id recommend using a library. Ive used https://github.com/Pomax/react-onclickoutside and it works very well, heres a snippet.
import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";
class MyComponent extends Component {
handleClickOutside = evt => {
// ..handling code goes here...
};
}
export default onClickOutside(MyComponent);
If you dont want to use a library use the onBlur
class MyComponent extends Component {
handleClickOutside = evt => {
// ..handling code goes here...
};
render(){
<div onBlur={this.handleClickOutside}>
<SomeChild />
</div>
}
}
Lastly your using React wrong, your using it as if it was jquery which it is not. you dont remove anything manually. You have state that you update when to close the dropdown and when to open it.
Using events such as window.onclick in render method is not a good practice since every time you update your state this event is going to be instantiated.
I also noticed you have created the state prop openmenu and myFunction but you not using them properly.
My suggestion is to attach DOM events in ReactJS's lifecycle events:
componentDidMount() {
window.addEventListener('onClick', this.myFunction)
}
componentWillUnmount() {
// Make sure to remove such event when your component is unmounted
window.removeEventListener('onClick', this.myFunction)
}
Related
I have a dropdown menu on a form. Once the user chooses a selection, I'd like for the Submit button to do different requests based on the dropdown menu item selection. For example, if user chooses Apples, then Submit button fires a function1, while if user chooses Bananas, then Submit button fires a function2.
I have a parent component that looks like this:
class Container extends React.PureComponent {
_onSubmit = (date) => {
this.props.function1({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
};
render() {
return (
<div>
<DownloadForm
dataSetName="Apples"
buttonText="Request Report"
onSubmit={this._onSubmit}
/>
</div>
);
}
}
Now, my DownloadForm component, which is nested here, will have a dropdown menu, and based on the dropdown menu's item, will have the onSubmit do something with a function2, which takes different parameters too.
I'm not sure how to do design this. I have 1 page as above, with the parent component that passes the onSubmit to it's nested component, and fires away to function1. I have another page as above, with the parent component also passing an onSubmit to the nested component, which fires away to function2.
How do I make it so that the dropdown menu item selection, inside the nested component, will then change it's button's onSubmit to fire away to function1 or function2 based on the dropdown menu item selected?
I hope my issue is clear.
Have a state that records the value of the dropdown selection inside the parent component. Inside _onSubmit() you check the state.
if (this.state.selection == "Apples") {
function1(param1, param2)
}
Here is a quick bit of code I put together that should get you started. It also takes into account some of #Mikes' comments
class Container extends React.Component {
constructor(props){
super(props);
this.state = {
selection: null
}
}
onSubmit = (date) => {
if (this.state.selection == "Apples") {
this.props.function1({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
}
if (this.state.selection == "Bananas") {
this.props.function2({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
}
};
handleSelectionChange = (selection) => {
this.setState({selection: selection})
}
render() {
return (
<div>
<Dropdown
onChange={this.handleSelectionChange}
/>
<DownloadForm
dataSetName="Apples"
buttonText="Request Report"
onSubmit={this._onSubmit}
/>
</div>
);
}
}
I am not explaining my entire code structure. Here is my current situation. I have a method added to child component inside my parent component :
<TableRow obj={object} key={i} delteMethod={that.deleteRow} />
and my child component is defined like the below :
<a href="#" onClick={this.props.delteMethod.bind(this, this.props.obj.id)}>Delete</a>
This is my deleteRow() function in the parent component :
deleteRow(deleteid) {
var that = this;
deleteItem(deleteid).then(function(data){
that.loadData();
});
}
This works. I am able to get the obj.id back in my parent component and I am doing rest of the tasks. What I want to do is, I want to add an event.preventDefault() function to the 'Delete' button click (the child component button which I said already working fine). How to achieve this?
You could define a function in your child component that will receive event and call your parent's function from props. Here is an example code for your child component:
constructor(props) {
super(props);
this.onDeleteClick = this.onDeleteClick.bind(this);
}
onDeleteClick(event) {
event.preventDefault();
this.props.delteMethod(this.props.obj.id);
}
render() {
return (<a href="#" onClick={this.onDeleteClick}>Delete</a>);
}
you can have a click handler function in your component class like this
clickHandler = (e) => {
this.props.delteMethod(this.props.obj.id);
e.preventDefault();
}
and on the button you can have it like
<a href="#" onClick={this.clickHandler}>Delete</a>
I am new to react so please be patient - I'm sure this is a simple problem but I am having a lot of trouble figuring out a solution.
I have a set of buttons, when they are clicked I add an 'active' className to that button and remove it from any other buttons that might be active.
I also need to open a panel with content based on which button is clicked.
So far I have managed to toggle the className of the clicked button, but can't figure out how to only apply it to the button that is clicked (see code below)
<a onClick={this.buttonClick(1)} className={this.state.isButtonActive ? 'active' : null}>button text</a>
<a onClick={this.buttonClick(2)} className={this.state.isButtonActive ? 'active' : null}>button text</a>
and the function to toggle the active state and open appropriate panel based on the button clicked:
buttonClick(buttonNumber) {
this.setState(prevState => ({
isButtonActive: !prevState.isButtonActive
}));
openPanel(buttonNumber)
}
I have experimented with creating a child component for the button and toggling the className within the component, but then I can't trigger the openPanel() function as it is in the parent component.
I hope that makes sense - thank you in advance :)
the problem is that you are sharing the same state for both buttons, so when you change it for one, it changes for the other. You should wrap your buttons in different components so that they have different state.
If you need a callback in the parent component to be called, pass it to the button components so that they can trigger that as well.
The button could look like this:
class Button extends React.Component {
constructor () {
super()
this.state = { isButtonActive: false }
this.onClick = this.onClick.bind(this)
}
onClick () {
this.setState({
isButtonActive: !this.state.isButtonActive
})
this.props.openPanel(this.props.buttonNumber)
}
render () {
return (
<button onClick={this.onClick()} className={this.state.isButtonActive ? 'active' : null}>button text</a>
)
}
}
how the parent component could look like:
class Parent extends React.Component {
onButtonClicked (number) {
console.log(`Button ${number} was clicked`)
}
render () {
return (
<div>
<Button buttonNumber={1} openPanel={this.onButtonClicked} />
<Button buttonNumber={2} openPanel={this.onButtonClicked} />
</div>
)
}
This happens because of the common state for both the button.
Instead of storing a boolean value for active, you can store the numeral like
<a onClick={this.buttonClick(1)} className={this.state.isButtonActive === 1 ? 'active' : null}>button text</a>
<a onClick={this.buttonClick(2)} className={this.state.isButtonActive === 2 ? 'active' : null}>button text</a>
and Click action to
buttonClick(buttonNumber) {
this.setState({
isButtonActive: buttonNumber
});
openPanel(buttonNumber)
}
I am fetching some data from the server to populate a list of items, and each item got a onClick event binded to the items id, that changes the UI to be disabled when clicked.
My problem is that the UI changes to disabled perfectly on the first click, but when I go on to click on the next item it resets the first one, so there is only one button disabled at a time. How do I make it so I can disable all the items I want, without resetting the previous state?
class Video extends Component {
constructor () {
super()
this.state = {
isDisabled: false
}
}
handleClick(frag, voted, event){
event.preventDefault()
this.setState({
isDisabled: {
[frag]: true
}
})
}
Snippet of what I return in the UI that changes the disabled button
<button onClick={this.handleClick.bind(this, frags.id, frags.voted)} disabled={this.state.isDisabled[frags.id]} className="rating-heart-2">
<i className="fa fa-heart" aria-hidden="true"></i>
</button>
I would really appreciate all tips!
You are completely replacing isDisabled each time you update state, but you really just want to add a new property to it. Try the following:
handleClick(frag, voted, event){
event.preventDefault();
let {isDisabled} = this.state;
isDisabled[frag] = true;
this.setState({isDisabled});
}
I have a Call To Action (CTA) which has a ripple effect, provided by a custom element called click-ripple, similar to Google Material Design. The custom element called click-ripple has a rule in the CSS to prevent this element from being clickable:
pointer-events: none;
If I do not add this rule, the element will be on top of its parent and it will not link the user through to the correct page or it will not perform the right action. Is there a way to feed an event from the parent through to one of its children without too much hassle?
EDIT
Let's say there is a button made up of a anchor-tag on the page. The markup of that anchor tag would look like this:
<template>
<a href="https://www.google.com">
<click-ripple></click-ripple>
</a>
</template>
My question is: what is an efficient way to feed a click action from the anchor tag forward to the click-ripple element?
The answer is to add an event listener to the parent of the current element. Looking it up in the W3 specification I came to the conclusion that I need to use element.parentElement.
clickripple.js
//Shortened for everyone's sanity
export class ClickRipple {
constructor(CssAnimator, Element) {
this.animator = animator;
this.element = element;
}
attached() {
this.element.parentElement.addEventListener("click", (event) => {
this.playAnimation(event);
});
}
playAnimation(event) {
//Math based on event
//Animation with Aurelia's CssAnimator
}
}
A common way of adding behavior (such as a click ripple) to an element is to use a custom attribute: <parent click-ripple></parent>.
Otherwise here's how you could pass the events to an arbitrary element:
click-through-custom-attribute.js
import {inject} from 'aurelia-framework';
#inject(Element)
export class ClickThroughCustomAttribute {
constructor(element) {
this.element = element;
this.handler = event => this.value.dispatchEvent(event);
}
attached() {
this.element.addEventListener('click', this.handler);
}
detached() {
this.element.removeEventListener('click', this.handler);
}
}
<require from="click-through-custom-attribute"></require>
<button ref="button1>I'm Behind</button>
<button click-through.bind="button1">I'm In Front</button>
If you know that the element you want to pass the click to is always going to be the parent element you could do something like this inside of your click-ripple custom element:
import {inject} from 'aurelia-framework';
#inject(Element)
export class ClickRipple {
constructor(element) {
this.element = element;
this.handler = event => this.element.parentElement.dispatchEvent(event);
}
attached() {
this.element.addEventListener('click', this.handler);
... add click ripple behavior ...
}
detached() {
... remove click ripple behavior ...
this.element.removeEventListener('click', this.handler);
}
}