Null isTrue
Null isTrue

Reputation: 1926

React - click event triggers all siblings at once

How could I have each <li /> that receives an onClick event to only fire individually one at a time?

My intent is to change colors and show/hide content based on a click event. It works, However upon clicking on a given <li/> all of its siblings get fired at the same time as well.

How could I prevent that?

here's sandbox code

function App() {
  return (
    <div className="App">
      <Market />
    </div>
  );
}

class Market extends Component {
  constructor() {
    super();
    this.state = {
      isColor: false,
      isShow: false,
      fruits: ["Apple", "Banana", "Peach"]
    };
  }

  handleToggle = () => {
    this.setState(currentState => ({
      isColor: !currentState.isColor,
      isShow: !currentState.isShow
    }));
  };

  render() {
    const fruits = this.state.fruits.map((item, i) => (
      <li
        key={i}
        className={this.state.isColor ? "blue" : "red"}
        onClick={this.handleToggle}
      >
        {item}
        <span className={this.state.isShow ? "show" : "hide"}>Show Text</span>
      </li>
    ));
    return <ul>{fruits}</ul>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 3

Views: 1840

Answers (3)

Vivek
Vivek

Reputation: 1525

Here's a working sandbox for the approach suggested by @SpeedOfRound. Creating an independent component for every li so that that elements has its own state. I don't know this approach is efficient compared to the answer by @Tholle.

SandBox [ https://codesandbox.io/s/1vyonx0zrl ]

Upvotes: 1

Tholle
Tholle

Reputation: 112917

All the click handlers are not fired simultaneously, but you keep one variable to indicate if all of them are shown or not.

You could instead keep an object in your state in which you keep key-value pairs indicating if an item is shown or not.

Example

class Market extends React.Component {
  state = {
    fruits: ["Apple", "Banana", "Peach"],
    isShown: {}
  };

  handleToggle = item => {
    this.setState(currentState => ({
      isShown: { ...currentState.isShown, [item]: !currentState.isShown[item] }
    }));
  };

  render() {
    return (
      <ul>
        {this.state.fruits.map((item, i) => (
          <li
            key={i}
            style={{
              backgroundColor: this.state.isShown[item] ? "blue" : "red"
            }}
            onClick={() => this.handleToggle(item)}
          >
            {item}
            <span
              style={{
                display: this.state.isShown[item] ? "inline-block" : "none"
              }}
            >
              Show Text
            </span>
          </li>
        ))}
      </ul>
    );
  }
}

ReactDOM.render(<Market />, 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>

Upvotes: 3

SpeedOfRound
SpeedOfRound

Reputation: 1278

Your <li>s are not all firing. All of your elements are checking state to see if color and visible is true. So you have one state, for three elements, when one is true, they all are true.

You either want to make an object in the state for each fruit, or better yet, create a new component for the list items that each have their own state, and pass the fruit name through a prop.

I've forked your sandbox and created a quick example of what this looks like. This is a core react concept of breaking things out into reusable modules, and having components manage their own state when possible. It will really benefit you in the long run to really get this idea down now, so I hope this helps.

Upvotes: 5

Related Questions