J'essaie de créer un compteur pour chaque élément d'une liste dans React. Je veux que chacun soit incrémenté ou décrémenté individuellement en fonction de ce sur quoi l'utilisateur clique. Ce problème est que tous les compteurs incrémentent et décrémentent en cliquant sur un seul élément, mais je voudrais que seul le compteur de l'élément cliqué change.

Voici mon code:

class name extends Component {

    constructor(){
        super()

        this.state = {
          news: [],
          voteing: 0
        }
    }

    onVoting(type){
        this.setState(prevState => {
           return {voteing: type == 'add' ? prevState.voteing + 1: prevState.voteing- 1}
        });
    }

    render() {
      return (
        <React.Fragment>
          <Content>
            {
              this.state.news.map((item, i)=>{
                return (
                <Item key={i}>
                  <text>
                    {item.subject}
                    {item.details}
                  </text>
                  <Votering>
                    <img src="" onClick={this.onVoting.bind(this, 'add')} />
                    <div value={this.state.voteing}>{this.state.voteing}</div>
                    <img src="" onClick={this.onVoting.bind(this, 'min')} />
                 </Votering>
                </Item>
                )
              })
            }
          </Content>
        </React.Fragment>
      )
    }
  }

J'essaye de faire ça:

<img src="" onClick={this.onVote(i).bind(this, 'add')} />

Mais ça ne marche pas aussi essayé this.onVote(item.i) et même résultat

1
muklah 20 nov. 2018 à 02:00

3 réponses

Meilleure réponse

La raison pour laquelle tous les décomptes de vos éléments changent lorsque l'un d'entre eux est cliqué est qu'ils partagent tous la même valeur de décompte de votes, voteing dans l'état du composant name.

Pour résoudre ce problème, vous devez diviser chaque élément en son propre composant avec état. Pour que chacun puisse suivre son propre nombre de clics.

Par exemple:

class name extends Component {
    constructor(){
        super();
        this.state = {
          news: []
        }
    }

    render() {
      return (
        <React.Fragment>
          <Content>
            {
              this.state.news.map((item, i) => {
                return <NewsItem key={ i }
                           subject={ item.subject }
                           details={ item.details }
                       />
              })
            }
          </Content>
        </React.Fragment>
      )
    }
  }

class NewsItem extends Component {
    constructor() {
        super();
        this.state = {
            voteCount = 0
        }
    }

    handleVote(type) {
        this.setState(prevState => ({
            voteCount: type === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
        }));
    }

    render() {
        const { subject, details } = this.props;
        const { voteCount } = this.state;

        return (
            <Item>
              <text>
                { subject }
                { details }
              </text>
              <Votering>
                <img src="" onClick={ this.handleVote.bind(this, 'add') } />
                <div value={ voteCount }>{ voteCount }</div>
                <img src="" onClick={ this.handleVote.bind(this, 'min') } />
             </Votering>
            </Item>
        )
    }
}

Vous pouvez également maintenir des décomptes séparés pour chaque élément dans le composant parent, mais je trouve que le fractionnement en composants séparés est beaucoup plus propre.

1
Henry Woody 19 nov. 2018 à 23:20

J'ai remarqué certaines choses sans rapport avec votre question.

1) onVoting doit être lié dans votre constructeur ou utiliser onVoting = () => { ..... }

2) dans votre fonction de rendu, vous avez onVote au lieu de onVoting

Sur votre question principale, dans votre état, vous ne maintenez qu'un seul compteur qui est affiché et modifié pour tous les éléments d'actualité. un moyen simple de contourner ce problème est de créer un nouvel élément de réaction pour chaque article de presse qui gérera le vote pour chaque article.

class parent extends Component {

constructor(){
    super()

    this.state = {
      news: null,
    }
}
componentDidMount() {
 // fetch data from api and minipulate as needed
 this.setState({news: dataFromApi})
}

render() {
  return (
      <Content>
        {
          this.state.news.map((item, i)=>{
            return (
            <NewChildComponent data={item}/>
            )
          })
        }
      </Content>
  )
}
}

class NewChildComponent extends Component {
    constructor() {
        super()
        this.state = {
             voting: 0,
        }
    }
    onVoting = (e) => {
       this.setState(prevState => ({
        voteCount: e.target.name === "add" ? prevState.voteCount + 1 : prevState.voteCount - 1
       }));
    }
    render () {
         const {data} = this.props;
         return (
            <Item key={data.uniqueID}>
              <text>
                {data.subject}
                {data.details}
              </text>
              <Votering>
                <img src="" onClick={this.onVoting} name="add"/>
                <div value={this.state.voteing}>{this.state.voteing}</div>
                <img src="" onClick={this.onVoting} name="min"/>
             </Votering>
            </Item>
           )
    }
}

Un petit aperçu des raisons pour lesquelles vous ne devriez pas lier votre fonction de rendu. https: // moyen. freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36

Voici pourquoi: Le composant parent transmet une fonction de flèche sur les accessoires. Les fonctions fléchées sont réallouées sur chaque rendu (même histoire avec l'utilisation de bind). Ainsi, bien que j'aie déclaré User.js comme PureComponent, la fonction de flèche dans le parent de l'utilisateur fait en sorte que le composant User voit une nouvelle fonction envoyée sur les accessoires pour tous les utilisateurs. Ainsi, chaque utilisateur effectue un nouveau rendu lorsque l'utilisateur clique sur un bouton de suppression. 👎

Aussi pourquoi vous ne devriez pas utiliser un index comme clé dans React. https://reactjs.org/docs/lists-and-keys.html

Nous ne recommandons pas d'utiliser des index pour les clés si l'ordre des éléments peut changer. Cela peut avoir un impact négatif sur les performances et peut entraîner des problèmes avec l'état des composants. Consultez l'article de Robin Pokorny pour une explication approfondie sur les impacts négatifs de l'utilisation d'un index comme clé. Si vous choisissez de ne pas affecter de clé explicite aux éléments de liste, React utilisera par défaut les index comme clés.

Voici une explication détaillée des raisons pour lesquelles les clés sont nécessaires si vous souhaitez en savoir plus.

1
joshua fermin 19 nov. 2018 à 23:42

Je ne vois pas vraiment comment vous aimeriez voir le vote comme faisant partie de la composante locale de l’État, comme il doit vraiment le faire (à mon avis), avec les entités sur lesquelles vous pouvez voter.

Donc, si j'étais vous, je réécrirais le code légèrement différent. Comme je ne sais pas ce que vous comptez faire par la suite avec les votes (cela suppose plutôt un processus en direct, ou au moins une sorte de bouton de sauvegarde, car il est enregistré ici dans l'état local VotingApp), je viens de tout enregistrer dans l'état local, comment vous géreriez ce n'est pas vraiment mon intention de répondre.

Donc personnellement, je préfère opter pour un composant fonctionnel, juste le rendu de l'actualité et sa capacité de vote, où le voteCount fait partie de l'entité d'élément. Si ce n'est pas ainsi que vous recevez les données, rien ne vous empêche d'ajouter les données après votre récupération et avant de les afficher réellement à l'écran. L'application elle-même recevra les modifications et l'élément qui sera modifié, et ce qu'elle fera ensuite, dépendra de vous;)

const { Component } = React;

const NewsItem = ( item ) => {
  const { subject, details, voteCount, handleVoteChange } = item;
  return (
    <div className="news-item">
      <div className="news-vote">
        <div className="vote-up" title="Vote up" onClick={ () => handleVoteChange( item, 1 ) }></div>
        <div className="vote-count">{ voteCount }</div>
        <div className="vote-down" title="Vote down" onClick={ () => handleVoteChange( item, -1 ) }></div>
      </div>
      <div className="news-content">
        <h3>{ subject }</h3>
        <div>{ details }</div>
      </div>
    </div>
  );
};

class VotingApp extends Component {
  constructor( props ) {
    super();
    this.handleVoteChange = this.handleVoteChange.bind( this );
    // by lack of fetching I add the initial newsItems to the state
    // and work by updating local state on voteChanges
    // depending on your state management (I guess you want to do something with the votes)
    // you could change this
    this.state = {
      newsItems: props.newsItems
    };
  }
  handleVoteChange( item, increment ) {
    this.setState( ( prevState ) => {
      const { newsItems } = prevState;
      // updates only the single item that has changed
      return { 
        newsItems: newsItems
          .map( oldItem => oldItem.id === item.id ? 
            { ...oldItem, voteCount: oldItem.voteCount + increment } : 
            oldItem ) };
    } );
  }
  render() {
    const { newsItems = [] } = this.state;
    return (
      <div className="kiosk">
        { newsItems.map( item => <NewsItem 
            key={ item.id } 
            {...item} 
            handleVoteChange={this.handleVoteChange} /> ) }
      </div>
    );
  }
}

// some bogus news items
const newsItems = [
  { id: 1, voteCount: 0, subject: 'Mars in 2020', details: 'Tesla will send manned BFR rockets to Mars in 2020' },
  { id: 2, voteCount: -3, subject: 'Stackoverflow rocks', details: 'Stackoverflow is booming thanks to the new friendly policy' },
  { id: 3, voteCount: 10, subject: 'DS9: Healthy living', details: 'Eat rice everyday and drink only water, and live 10 years longer, says Dax to Sisko, Sisko suprises her by saying that like that, he doesn\'t want to live 10 years longer...' }
];

// render towards the container
const target = document.querySelector('#container');
ReactDOM.render( <VotingApp newsItems={ newsItems } />, target );
.kiosk {
  display: flex;
  flex-wrap: no-wrap;
}
.news-item {
  display: flex;
  justify-content: flex-start;
  width: 100%;
}
.news-vote {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-left: 10px;
  padding-right: 10px;
}
.news-vote > * {
  cursor: pointer;
}
.news-content {
  display: flex;
  flex-direction: column;
}
.vote-up::before {
  content: '▲';
}
.vote-down::before {
  content: '▼';
}
.vote-up:hover, .vote-down:hover {
  color: #cfcfcf;
}
h3 { margin: 0; }
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="prop-types" src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
2
Icepickle 20 nov. 2018 à 00:12