1

I want to add sub categories with react on a list, but when i click on sub cat, i have two events : First on sub and second on parent category.

How can i have only child category ?

There is my actual code :

getList(myList){
    return myList.map((item) => {
        let subList = '';
        if (item.hasSub){
            subList = (<ul>{this.getList(item.sub)}</ul>)
        }
        return (
        <li onClick={() => {this.props.clicHandler(item.id)}}>{item.title}<div>{subList}</div></li>);
    })
}

I'm using recursive method to create my list. My actual array is like this :

this.list.push({id: 1, title:'coucou' , hasSub: false});
this.list.push({id: 2, title:'toto' , hasSub: false});
this.list.push({id: 3, title: 'cat', sub: [{id:4, title:'titi' , hasSub: false}, {id:5, title:'tutu' , hasSub: false}] , hasSub: true});

Thank you for your help !

Psyycker
  • 317
  • 4
  • 16

2 Answers2

1

You need to prevent the event propagation :

getList(myList){
    return myList.map((item) => {
        let subList = '';
        if (item.hasSub){
            subList = (<ul>{this.getList(item.sub)}</ul>)
        }
        return (
        <li onClick={(event) => {event.preventDefault(); this.props.clicHandler(item.id)}}>{item.title}<div>{subList}</div></li>);
    })
}

This way the first item to catch the click will stop the click propagation.

Julien TASSIN
  • 5,004
  • 1
  • 25
  • 40
1

The problem is that click events "bubble" up to parent elements. The solution is to call Event.prototype.stopPropagation on it.

In the below snippet I've added a clickHandler method to the component, and in getList I pass both the event (evt) and item.id to it. In that method I call evt.stopPropagation() before calling this.props.clickHandler. However, you could also do this in the parent component's clickHandler method, or within getList. Choose whatever best suits your use case.

class App extends React.Component {
  constructor() {
    super();
    this.clickHandler = this.clickHandler.bind(this);
  }
  
  render() {
    return <ul>{this.getList(this.props.items)}</ul>;
  }

  getList(list) {
    return list.map((item) => {
      const subList = item.hasSub && (<ul>{this.getList(item.sub)}</ul>);
      return (
        <li key={item.id} onClick={evt => {this.clickHandler(evt, item.id)}}>{item.title}<div>{subList}</div></li>
      );
    });
  }
  
  clickHandler(evt, itemId) {
    // Stop event propagatation before calling handler passed in props
    evt.stopPropagation();
    this.props.clickHandler(itemId);
  }
}

const data = [
  { id: 'a', title: 'A', hasSub: false },
  { id: 'b', title: 'B', hasSub: true, sub: [
    { id: 'b1', title: 'B1', hasSub: true, sub: [
      { id: 'b1a', title: 'B1a', hasSub: false },
    ] },
    { id: 'b2', title: 'B2', hasSub: false },
  ] },
  { id: 'c', title: 'C', hasSub: true, sub: [
    { id: 'c1', title: 'C1', hasSub: false },
    { id: 'c2', title: 'C2', hasSub: false },
  ] },
];

function clickHandler(itemId) {
  console.log('clicked item', itemId);
}

ReactDOM.render(<App items={data} clickHandler={clickHandler}/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div></div>

P.S. Your code is excellent, but might I recommend a somewhat more "React way" to render nested items?

What your component's render method does is call a getItems function that in turn calls itself, recursively. However, if you recall that most React components can themselves be thought of as functions, you can refactor your code so that your render method instead renders an <Items> component that recursively renders instances of itself.

This approach tends to yield smaller, functional components that are more reusable and easier to test. Take a look in the snippet below.

const Items = ({items, onClick}) => (
  <ul>
    {items.map(item => (
      <li key={item.id} onClick={evt => onClick(evt, item.id)}>
        {item.title}
        {item.hasSub && <Items items={item.sub} onClick={onClick}/>}
      </li>
    ))}
  </ul>
);

const data = [
  { id: 'a', title: 'A', hasSub: false },
  { id: 'b', title: 'B', hasSub: true, sub: [
    { id: 'b1', title: 'B1', hasSub: true, sub: [
      { id: 'b1a', title: 'B1a', hasSub: false },
    ] },
  ] },
];

function clickHandler(evt, itemId) {
  evt.stopPropagation();
  console.log('clicked item', itemId);
}

ReactDOM.render(<Items items={data} onClick={clickHandler}/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div></div>
Community
  • 1
  • 1
Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • Yes you're right ! It's just test code, so my code is a little dirty, but i'll think about when i'll turn it to real code :) And thank you for you're first part of code ! I what i want ! – Psyycker Apr 11 '17 at 08:32