简体   繁体   中英

Why is this onClick event handler firing twice in my create-react-app

can someone tell me why is this "upvote" onClick handler firing twice? the logs would indicate it's only running once but the score it controls increases by 2

export default class Container extends Component {
  constructor(props) {
    super(props);

    this.state = {
      jokes: [],
    };
    this.getNewJokes = this.getNewJokes.bind(this);
    this.retrieveJokes = this.retrieveJokes.bind(this);
    this.upVote = this.upVote.bind(this);
  }
upVote(id) {
    this.setState(state => {
      //find the joke with the matching id and increase score by one
      const modifiedJokes = state.jokes.map(joke => {
        if (joke.id === id) {
          
          joke.score = joke.score + 1;
          
        }
        return joke;
      });
      console.log(modifiedJokes);
      return { jokes: modifiedJokes };
    });
  }
render() {
    return (
      <div>
        <h1>Container</h1>
        {this.state.jokes.map(joke => (
          <Joke
            key={joke.id}
            id={joke.id}
            joke={joke.joke}
            score={joke.score}
            upVote={this.upVote}
            downVote={this.downVote}
          />
        ))}
      </div>
    );
  }
}

on the other hand if I rewrite the handler this way, then it fires only once

upVote(id) {
    const modifiedJokes = this.state.jokes.map(joke => {
      if (joke.id === id) {
        joke.score = joke.score + 1;
      }
      return joke;
    });
    this.setState({ jokes: modifiedJokes });
  };

This doesn't work because React uses synthetic events which are reused when it's handling is done. Calling a function form of setState will defer evaluation (this can be dangerous when the setState call is event dependent and the event has lost its value).

So this should also work:

this.setState((state,props) => ({ jokes: modifiedJokes}));

I can't locate my source at the moment but, I remember coming across this a while back. I'm sure looking through the React Docs can provide a more in depth explanation

My best guess is that in the first case, you are also modifying the state directly, when you do joke.score = joke.score + 1;

Because you are doing this mapping directly on state array variable, and in Javascript, when using array, you are only working with pointer to that array, not creating a copy of that array.

So the mapping function probably takes a shallow copy of the array, and there's where problem happens.

You can use lodash to create a deep copy of the state array before you going to work with it, which will not cause your problem:

https://codesandbox.io/s/great-babbage-lorlm

I found out what was wrong. I'll post an answer just in case anyone happens to stumble into this upon encountering the same problem.

When in debug mode react will run certain functions twice to discourage certain operations that might result in bugs. One of these operations is directly modifying state. when doing

this.setState(state => {
      //find the joke with the matching id and increase score by one
      const modifiedJokes = state.jokes.map(joke => {
        if (joke.id === id) {
          
          joke.score = joke.score + 1;
          
        }
        return joke;
      });
      console.log(modifiedJokes);
      return { jokes: modifiedJokes };

the map function returns a new array where every elements points to the same element in the original array. therefore by doing


state.jokes.map(joke => {
        if (joke.id === id) {          
          joke.score = joke.score + 1;          
        }

I was effectively directly modifying the state. React runs the setstate function twice in debug mode to weed out this kind of operation.

so the correct "react way"of modifying the attribute of an object nested in an array is to do this instead

this.setState(state =>{
   let modifiedJokes = state.jokes.map(joke => {
        
        if (joke.id === id) {          
          return {...joke, score:joke.score+1}         
        }
        else{ return joke}
   })
   return {jokes:modifiedJokes}
}) 

this way when encountering an element with the correct ID a copy is made of that specific element and the score of that copy is increased by one which doesn't impact the actual element which is still in the state left untouched until it is modified by react committing the new state

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM