1
votes

Activer / désactiver l'objet d'état imbriqué dans React

J'ai un objet d'état qui contient un tableau d'objets:

export default class StatePractice extends React.Component {

  constructor() {
    super();
    this.state = {
      feeling: [
        { name: 'alert', status: false },
        { name: 'calm', status: false },
        { name: 'creative', status: false },
        { name: 'productive', status: false },
        { name: 'relaxed', status: false },
        { name: 'sleepy', status: false },
        { name: 'uplifted', status: false }
      ]
    }
  }

  buttonToggle = (event) => {
    event.persist();
    const value = !event.target.value

    this.setState( prevState => ({
      status: !prevState.status
    }))
  }



  render() {
    return (  
      <div>
        { this.state.feeling.map(
            (stateObj, index) => { 
              return <button 
                key={ index }
                onClick={ this.buttonToggle } 
                value={ stateObj.status } >
                  { stateObj.status.toString() }
                </button>
            }
          )
        }
      </div>
    )
  }
}  

Je veux faire basculer le status booléen de vrai à faux lors d'un événement de clic. J'ai construit cette fonction en tant que gestionnaire de clics mais cela ne connecte pas l'événement au changement d'état:

buttonToggle = (event) => {
  event.persist();
  const value = !event.target.value

  this.setState( prevState => ({
    status: !prevState.status
  }))
}

J'ai du mal à suivre le flux de contrôle de l'imbriqué Réagir au changement d'état, et comment l'événement actif fait le saut du gestionnaire à l'objet d'état et vice versa.

L'ensemble du composant:

this.state = {
  feeling: [
    { name: 'alert', status: false },
    { name: 'calm', status: false },
    { name: 'creative', status: false },
    { name: 'productive', status: false },
    { name: 'relaxed', status: false },
    { name: 'sleepy', status: false },
    { name: 'uplifted', status: false }
  ]
}


3 commentaires

Je veux faire basculer le statut booléen de vrai à faux lors d'un événement de clic. Dans tous les objets ou dans un objet spécifique?


Votre état initial contient un tableau d'objets, mais vous passez ensuite l'objet single dans setState . C'est probablement le problème.


@hindmost - Je veux juste basculer le statut sur un élément à la fois - celui sur lequel on clique.


3 Réponses :


2
votes

Afin de résoudre votre problème, vous devez d'abord envoyer l'index de l'élément qui va être modifié à votre fonction bascule:

buttonToggle = index => event => {
    event.persist();
    const feeling = [...this.state.feeling]; //Copy your array
    feeling[index] = !feeling[index];
    this.setState({ feeling }); 
}

Ensuite, ajustez la fonction pour recevoir à la fois l'index et l'événement.

Maintenant, pour modifier votre tableau d'état, copiez-le, changez la valeur que vous recherchez et remettez-le dans votre état:

onClick = {this.buttonToggle(index)} 

Vous pouvez également utiliser slice pour copier votre tableau, ou même envoyer directement un tableau mappé où une seule valeur est modifiée.


0 commentaires

1
votes

La mise à jour d'un objet imbriqué dans un objet à état de réaction est délicate. Vous devez obtenir l'objet entier de l'état dans une variable temporaire, mettre à jour la valeur dans cette variable, puis remplacer l'état par la variable mise à jour. Pour ce faire, votre fonction buttonToggle doit savoir quel bouton a été enfoncé.

 buttonToggle = (event, name) => {
   event.persist();
   let { feeling } = this.state;
   feeling[name] = !feeling[name];
   this.setState({
    feeling
   });
 }

Et votre fonction buttonToggle pourrait ressembler à ceci

 feeling: {
    alert: false,
    calm: false,
    creative: false,
    etc...
  }

Voici un JSFiddle fonctionnel.

Alternativement, si vous n'en avez pas besoin pour stocker plus de données par sentiment que "nom" et "statut", vous pouvez réécrire l'état de votre composant comme ceci:

buttonToggle = (event, name) => {
  event.persist();

  let { feeling } = this.state;
    let newFeeling = [];

  for (let index in feeling) {
    let feel = feeling[index];
    if (feel.name == name) {
        feel = {name: feel.name, status: !feel.status};
    }
    newFeeling.push(feel);
  }

  this.setState({
    feeling: newFeeling,
  });
}

Et buttonToggle:

return <button 
  key={ index }
  onClick={ (event) => this.buttonToggle(event, stateObj.name) } 
  value={ stateObj.status } >
    { stateObj.status.toString() }
  </button>


3 commentaires

Excellente réponse, bienvenue à SO. Le consensus semble être que vous devez réécrire le tableau entier dans setState. Est-ce une exigence, semble très peu performant.


@staypuftman C'est redondant, mais actuellement le seul moyen. React ne gère pas très bien l'état imbriqué. Une approche alternative serait soit de 1. concevoir des composants de réaction (plus petits) qui effectuent une tâche spécifique (et de réduire la complexité de l'objet d'état) ou 2. d'envisager d'implémenter un redux dans react. Je recommande d'examiner les deux.


intéressant, explique pourquoi toutes mes tentatives n'ont pas fonctionné - je n'avais pas le bon cadre. Également intéressant de mentionner redux. Ce morceau d'État est en fait dans un contexte en ce moment, mais il semble que j'ai atteint la limite de ce que cela peut faire. Mon site n'est pas super fou donc je pense que ça ira. Merci encore.



0
votes

Je pense que vous devez mettre à jour tout le tableau lorsque vous recevez l'événement. Et il vaut mieux ne pas muter l'état existant. Je recommanderais le code suivant

export default class StatePractice extends React.Component {
  constructor() {
    super();
    this.state = {
      feeling: [
        { name: "alert", status: false },
        { name: "calm", status: false },
        { name: "creative", status: false },
        { name: "productive", status: false },
        { name: "relaxed", status: false },
        { name: "sleepy", status: false },
        { name: "uplifted", status: false },
      ],
    };
  }

  buttonToggle = (index, value) => (event) => {
    event.persist();
    const toUpdate = { ...this.state.feeling[index], status: !value };
    const feeling = [...this.state.feeling];
    feeling.splice(index, 1, toUpdate);
    this.setState({
      feeling,
    });
  };

  render() {
    return (
      <div>
        {this.state.feeling.map((stateObj, index) => {
          return (
            <button
              key={index}
              onClick={this.buttonToggle(index, stateObj.status)}
              value={stateObj.status}
            >
              {stateObj.status.toString()}
            </button>
          );
        })}
      </div>
    );
  }
}


2 commentaires

Donc, vous avez imbriqué l'événement dans une autre fonction de buttonToggle - pourquoi?


J'ai imbriqué le buttonToggle pour renvoyer la fonction parce que je veux réutiliser l'index et la valeur de statut de l'élément et le gestionnaire d'événements est une fonction, donc je les combine simplement :)