3

I am generating a nested lists from a json file.I converted that json file to an array tree. Here I wanted to make a drop down list using that array tree where onSidebarComponent showing the data according to that. I used array mapping and pushed the data into array and showing them.

But whenever I am clicking the 1st index data it firing a single click event. whenever I am clicking the leaf child node data it firing 3 click events. On the other hand I don't know how to handle dynamically showing and hiding the single list data. I am giving my code and some screenshot for better understand.

here is the code:

import React, { Component } from 'react';
import './App.css';
import data from './data/categories.json'
import { list_to_tree, search_from_tree } from './data/supportFunc'

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      search: "",
      show1: false,
      show2: false,
      show3: false
    }
  }
  //----------------------------------------------------------------------------------------
  onChangeHandler(e) {
    this.setState({
      search: e.target.value
    })
  }

  //----------------------------------------------------------------------------------------
  onNavPressed1(e, i) {
    e.preventDefault()
    console.log("button pressed1")
    console.log(i)
    this.setState({
      show1: i,
      search: ""
    })
  }
  //----------------------------------------------------------------------------------------
  onNavPressed2(e, i) {
    e.preventDefault()
    console.log("button pressed2")
    console.log(i)
    this.setState({
      show2: i,
    })
  }
  //----------------------------------------------------------------------------------------
  onNavPressed3(e, i) {
    e.preventDefault()
    console.log("button pressed3")
    console.log(i)
    this.setState({
      show3: i
    })
  }
  //----------------------------------------------------------------------------------------
  sideBarComponent(listData) {
    if (this.state.search !== "") {
      var clsName = "navbar-nav padd"
    }
    else {
      var clsName = "navbar-nav padd collapse"
    }

    let arr = []
    listData.map((d, i) => {
      let subarr = []
      d.children.map((child, j) => {
        let subChildArr = []
        child.children.map((subChild, k) => {
          subChildArr.push(<li onClick={(e) => this.onNavPressed3(e, "_" + k)} className="nav-item listItem" key={i + "_" + j + "_" + k}>
            {subChild.Name} <span className="float-right"> {subChild.children.length !== 0 ? <i className="fas fa-angle-right"></i> : " "}</span>
          </li>)
        })
        subarr.push(<li onClick={(e) => this.onNavPressed2(e, "_" + j)} className="nav-item listItem" key={i + "_" + j}>
          {child.Name} <span className="float-right"> {child.children.length !== 0 ? <i className="fas fa-angle-right"></i> : " "}</span>
          <ul className={clsName}>{subChildArr}</ul>
        </li>)
      })
      arr.push(<li onClick={(e) => this.onNavPressed1(e, "_" + i)} className="nav-item listItem" key={i}>
        {d.Name} <span className="float-right ">{d.children.length !== 0 ? <i className="fas fa-angle-right"></i> : " "}</span>
        <ul className={clsName}>{subarr}</ul>
      </li>)
    })
    return (<nav className="navbar">
      <ul className="navbar-nav navbar-width">
        {arr}
      </ul>
    </nav>
    )
  }
  //----------------------------------------------------------------------------------------
  render() {
    var val = this.state.search.toUpperCase()
    var listData = list_to_tree(data)
    var newListData = search_from_tree(listData, val)
    // console.log("listData======")
    // console.log(newListData)
    return (
      <div className="App">
        <header className="App-header">
          <input type="text"
            name="search"
            className="App-search"
            onChange={(e) => this.onChangeHandler(e)}
            value={this.state.search}
            placeholder="Enter Products Category"
          />
        </header>
        <div className="nav-container">
          {this.sideBarComponent(newListData)}
        </div>
      </div>
    );
  }
}

export default 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>

here is the screenshot enter image description here

bpsourav21
  • 833
  • 7
  • 10

1 Answers1

4

What's happening?

That behavior is caused by the event propagation. There are two types of event propagation - Event bubbling and Capturing.

Event bubbling - triggers the events of the innermost elements firstly and then propagates the events to the outer elements , i.e. inside -> out.

Event capturing - triggers the events of the outermost elements firstly and then propagates the events to the inner elements, i.e. out -> inside.

Solution:

Depending on your use-case, here we're talking about Event bubbling. So in order to prevent the event propagation to the the parent elements, you have to stop the event's propagation via e.stopPropagation() in your nested items onClick handler.

I guess it's perfectly fine to invoke e.stopPropagation() in your onNavPressed handlers.

However, here's a basic example, that illustrate cleaner how to prevent Event Bubbling in React (credits):

class List extends React.Component {
  handleClick = e => {
    // do something
  }

  render() {
    return (
      <ul onClick={this.handleClick}>
        <ListItem onClick={this.handleClick}>Item</li> 
      </ul>
    )
  }
}

class ListItem extends React.Component {
  handleClick = e => {
    e.stopPropagation();
    this.props.onClick();
  }

  render() {
    return (
      <li onClick={this.handleClick}>
        {this.props.children}
      </li>       
    )
  }
}

Credits:

  1. What is event bubbling and capturing?
  2. React prevent event bubbling in nested components on click
  3. Example for Bubbling and Capturing in React.js
Jordan Enev
  • 16,904
  • 3
  • 42
  • 67