event.target is taking react child components separately - javascript

I have a menu box in my react app and I show when I click on show menu button and hiding it if menu its open. I want to hide menu if I click outside the menu
class MenuButton extends Component {
constructor (props) {
super (props)
this.state = {showMenu: false}
this.toggleMenu = this.toggleMenu.bind(this)
}
toggleMenu () {
let showMenu = !this.state.showMenu
this.setState({showMenu})
}
componentDidMount () {
window.addEventListner('click', e => {
if (e.target !== document.getElementById('menu-div') {
this.setState({showMenu: false})
}
})
}
render () {} {
return (
<div>
<button onClick={this.toggleMenu}>Menu</button>
{this.state.showMenu ? <div id='menu-div><Menu /></div> : null}
</div>
)
}
}
and my Menu Component has many child Components
const Menu = () => {
return (
<div>
<Component1/>
<Component2/>
<Component3/>
</div>
)
}
but clicking on these child components close my menu as event.target is giving different node

I would not use getElementById nor search elements in the DOM. This is not the "React Way" of doing things and it's considered a bad practice.
Instead use the refs API that react provides and grab a reference to the node.
You can add event listener of mousedown and check if the ref contains the target, if not then it means you are outside the ref (the menu in your case). So all is left is to set the state to close it.
Here is a running snippet of your code with the implementation i mentioned:
class MenuButton extends React.Component {
constructor(props) {
super(props);
this.state = { showMenu: false };
}
componentDidMount() {
document.addEventListener("mousedown", this.handleOutsideClick);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleOutsideClick);
}
toggleMenu = () => {
let showMenu = !this.state.showMenu;
this.setState({ showMenu });
};
handleOutsideClick = event => {
if (this.menuRef && !this.menuRef.contains(event.target)) {
this.setState({ showMenu: false });
}
};
setRef = ref => {
this.menuRef = ref;
};
render() {
const { showMenu } = this.state;
return (
<div>
<button onClick={this.toggleMenu}>Menu</button>
{<Menu className={`${!showMenu && "hide"}`} setRef={this.setRef} />}
</div>
);
}
}
class Menu extends React.Component {
render() {
const { setRef, className } = this.props;
return (
<div className={`menu ${className}`} ref={setRef}>
<div>comp 1</div>
<div>comp 2</div>
<div>comp 3</div>
</div>
);
}
}
ReactDOM.render(<MenuButton />, document.getElementById("root"));
.hide{
display: none;
}
.menu{
border: 1px solid #333;
padding: 5px;
max-width: 60px;
}
<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>

Related

React onClick event in array.map()

Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;

Remove class name sibling component when click the current component and make toggling in current to remove class it self

i'm new in react and try to make a button that will remove the sibling class if i click in the current button, and also the current button can remove it self class (toggling),i already try and it seems im not find the clue. here my code below .
export default class Child extends Component {
render(){
return(
<button
onClick={this.props.onClick}
className={this.props.activeMode ? 'active' : ''}>
{this.props.text}
</button>
)
}
}
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: null,
};
}
handleClick (selectedIndex) {
this.setState({
selectedIndex,
});
}
render () {
const array = ["Button 1","Button 2"];
return (
<div>
{array.map((obj, index) => {
const active = this.state.selectedIndex === index;
return <Child
text={obj}
activeMode={active}
key={index}
onClick={() => this.handleClick(index)} />
})}
</div>
)
}
}
I'm not shure that I understood your clearly right
Did you meen something like this?
class Child extends React.Component {
render(){
return(
<button
onClick={this.props.onClick}
className={this.props.activeMode ? 'active' : ''}>
{this.props.text}
</button>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: null,
};
}
handleClick (selectedIndex) {
let selected = this.state.selectedIndex === selectedIndex ? null : selectedIndex;
this.setState({
selectedIndex: selected,
});
}
render () {
const array = ["Button 1","Button 2"];
return (
<div>
{
array.map((obj, index) => {
return <Child text={obj}
activeMode={this.state.selectedIndex === index}
key={index}
onClick={() => this.handleClick(index)} />
})
}
</div>
)
}
}
ReactDOM.render(
<Parent name="World" />,
document.getElementById('container')
);
.active {
color: green;
}
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>

ReactJS: How to pass keycode to another component

There is an input field and another component. I need to get the event keyCode to the second component.
I don't get both components connected as some posts said ref is not the best attempt for this. I don't really understand that...
Parent
class Parent extends Component {
render () {
return(
<Input onKeyDown={() => { console.log('triggered keydown') }} />
<Child />
)
}
}
Child
class Child extends Component {
handleKeyDown (e) {
console.log(e.keyCode) // <- Get Keycode from parent triggered keydown event
}
render () {
}
}
Use setState() to update the current keyCode in state, and pass it to the child via prop.
In the child you can use the prop directly or process it in componentWillReceiveProps(), and set the result to the state.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
onKeyDown = (e) => this.setState({ keyCode: e.keyCode });
render() {
const { keyCode } = this.state;
return (
<div>
<input onKeyDown={this.onKeyDown} />
<Child keyCode={keyCode} />
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
doubleKeyCode: []
};
}
handleKeyDown = (keyCode = 0) => this.setState((prevState) => ({
doubleKeyCode: [...prevState.doubleKeyCode, keyCode * 2]
}));
componentWillReceiveProps(nextProps) {
this.handleKeyDown(nextProps.keyCode);
}
render () {
const { doubleKeyCode } = this.state;
console.log(`render: ${doubleKeyCode}`);
return (
<div>{doubleKeyCode.join(', ')}</div>
);
}
}
ReactDOM.render(
<Parent />,
demo
);
<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="demo"></div>

Prevent setState trigger in React

I have:
class SomeComponent extends Component {
state = {
outside: false,
inside: false,
}
onOutsideClick = () => this.setState({ outside: true })
onInsideClick = () => this.setState({ inside: true })
render() {
return (<div onClick={this.onOutsideClick}>Some text, <p onClick={this.onInsideClick}>some other text</p></div>)
}
}
When I click on some other text, the onOutsideClick handler will trigger as well and as such this.state.outside will change to true. If some other text is clicked, I don't want to trigger any other method.
I tried with e.stopPropagation and if (this.state.inside) return in onOutsideClick but none of those worked
You should use event.stopPropagation() to stop bubbling event. It's hard to say why stopPropagation() hasn't worked for you.
Working demo with stopPropagation(): https://codesandbox.io/s/wpPJ1LkXR
import React, { Component } from 'react';
import { render } from 'react-dom';
const INSIDE_CLICK = 'inside';
const OUTSIDE_CLICK = 'outside';
class App extends Component {
state = {
clicked: undefined,
};
onOutsideClick = () => {
this.setState({ clicked: OUTSIDE_CLICK });
};
onInsideClick = (event) => {
event.stopPropagation();
this.setState({ clicked: INSIDE_CLICK });
};
render() {
return (
<div onClick={this.onOutsideClick}>
Some text, <p onClick={this.onInsideClick}>some other text</p>
<div style={{ border: '2px solid red' }}>
Clicked: {this.state.clicked}
</div>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Check some boolean before calling setState and put it to false again in the setState callback.

React animation not working

I have a Notification class to manage creating/removing Notifications from the UI. I can successfully show/hide notifications, but the notifications do not animate in/out.
import React from 'react'
import addons from 'react/addons'
const ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
let id = 0
export default class Notification {
constructor (content, className) {
let div = document.createElement('div')
document.body.appendChild(div)
let onClose = () => React.unmountComponentAtNode(div)
React.render(
<NotificationElement className={ className } id={ id++ } onClose={ onClose }>
{ content }
</NotificationElement>,
div
)
}
}
class NotificationElement extends React.Component {
constructor(_) {
super(_)
}
render() {
const className = `Notification ${ this.props.className }`
return (
<ReactCSSTransitionGroup transitionName="slideIn">
<div className={ className } key={ this.props.id }>
{ this.props.children }
<a className="close-button" onClick={ this.props.onClose }>×</a>
</div>
</ReactCSSTransitionGroup>
)
}
}
ReactCSSTransitionGroup will only animate its children when they are added or removed from its props. In your case, the children prop of your ReactCSSTransitionGroup never changes.
Here's an example of what you could do instead:
let notificationsContainer = document.createElement('div');
document.body.appendChild(notificationsContainer);
let notifications = [];
function renderNotifications() {
React.render(<Notifications notifications={notifications} />, notificationsContainer);
}
class Notifications extends React.Component {
render() {
return (
<ReactCSSTransitionGroup transitionName="slideIn">
{this.props.notifications.map(notification =>
<NotificationElement
key={ notification.id }
className={ notification.className }
onClose={ notification.onClose }
children={ content }
/>
)}
</ReactCSSTransitionGroup>
);
}
}
let id = 0
export default class Notification {
constructor (content, className) {
let notification = {
id: id++, content, className,
};
notification.onClose = () => {
notifications.splice(notifications.indexOf(notification), 1);
renderNotifications();
};
notifications.push(notification);
renderNotifications();
}
}
class NotificationElement extends React.Component {
constructor(_) {
super(_)
}
render() {
const className = `Notification ${ this.props.className }`
return (
<div className={ className }>
{ this.props.children }
<a className="close-button" onClick={ this.props.onClose }>×</a>
</div>
)
}
}
Every time a notification is added or removed, its corresponding element is added or removed from the children prop of ReactCSSTransitionGroup, which can then animate it in or out properly.
You can add transitionAppear={true} to your <ReactCSSTranstionGroup /> to make it animate the initial render.
It is disabled by default: https://github.com/facebook/react/blob/master/src/addons/transitions/ReactCSSTransitionGroup.js#L38

Categories

Resources