web-dev-qa-db-fra.com

ReactJS: Warning: setState (...): Impossible de mettre à jour pendant une transition d'état existante

J'essaie de refactoriser le code suivant à partir de ma vue de rendu:

<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange.bind(this,false)} >Retour</Button>

à une version où la liaison est dans le constructeur. La raison en est que lier dans la vue de rendu me posera des problèmes de performances, en particulier sur les téléphones mobiles bas de gamme. 

J'ai créé le code suivant, mais je reçois constamment les erreurs suivantes (beaucoup d'entre elles). On dirait que l'application se met en boucle:

Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.

Ci-dessous le code que j'utilise:

var React = require('react');
var ButtonGroup = require('react-bootstrap/lib/ButtonGroup');
var Button = require('react-bootstrap/lib/Button');
var Form = require('react-bootstrap/lib/Form');
var FormGroup = require('react-bootstrap/lib/FormGroup');
var Well = require('react-bootstrap/lib/Well');

export default class Search extends React.Component {

    constructor() {
        super();

        this.state = {
            singleJourney: false
        };

        this.handleButtonChange = this.handleButtonChange.bind(this);
    }

    handleButtonChange(value) {
        this.setState({
            singleJourney: value
        });
    }

    render() {

        return (
            <Form>

                <Well style={wellStyle}>

                    <FormGroup className="text-center">

                        <ButtonGroup>
                            <Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange(false)} >Retour</Button>
                            <Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChange(true)} >Single Journey</Button>
                        </ButtonGroup>
                    </FormGroup>

                </Well>

            </Form>
        );
    }
}

module.exports = Search;
141
user3611459

On dirait que vous appelez accidentellement la méthode handleButtonChange dans votre méthode de rendu, vous voudrez probablement faire onClick={() => this.handleButtonChange(false)} à la place.

Si vous ne voulez pas créer un lambda dans le gestionnaire onClick, je pense que vous aurez besoin de deux méthodes liées, une pour chaque paramètre.

Dans la constructor:

this.handleButtonChangeRetour = this.handleButtonChange.bind(this, true);
this.handleButtonChangeSingle = this.handleButtonChange.bind(this, false);

Et dans la méthode render:

<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChangeSingle} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChangeRetour}>Single Journey</Button>
254

From react docs Passage d'arguments aux gestionnaires d'événements

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
2
Rahil Ahmad

Si vous essayez d'ajouter des arguments à un gestionnaire dans recompose, assurez-vous de définir vos arguments correctement dans le gestionnaire. Il s’agit essentiellement d’une fonction curry, vous voulez donc vous assurer d’exiger le nombre correct d’arguments. Cette page a un bon exemple d'utilisation d'arguments avec des gestionnaires.

Exemple (à partir du lien):

withHandlers({
  handleClick: props => (value1, value2) => event => {
    console.log(event)
    alert(value1 + ' was clicked!')
    props.doSomething(value2)
  },
})

pour votre enfant HOC et chez le parent

class MyComponent extends Component {
  static propTypes = {
    handleClick: PropTypes.func, 
  }
  render () {
    const {handleClick} = this.props
    return (
      <div onClick={handleClick(value1, value2)} />
    )
  }
}

cela évite d’écrire une fonction anonyme de votre gestionnaire pour corriger le problème en ne fournissant pas suffisamment de noms de paramètres sur votre gestionnaire.

2
Nick Brady

Je donne un exemple générique pour une meilleure compréhension, dans le code suivant

render(){
    return(
      <div>

        <h3>Simple Counter</h3>
        <Counter
          value={this.props.counter}
          onIncrement={this.props.increment()} <------ calling the function
          onDecrement={this.props.decrement()} <-----------
          onIncrementAsync={this.props.incrementAsync()} />
      </div>
    )
  }

Lors de la fourniture d'accessoires, j'appelle la fonction directement, cette opération a une exécution en boucle infinie et vous renvoie l'erreur. Supprimez l'appel de fonction, tout fonctionne normalement 

render(){
    return(
      <div>

        <h3>Simple Counter</h3>
        <Counter
          value={this.props.counter}
          onIncrement={this.props.increment} <------ function call removed
          onDecrement={this.props.decrement} <-----------
          onIncrementAsync={this.props.incrementAsync} />
      </div>
    )
  }
1
Ignatius Andrew

J'ai eu la même erreur quand j'appelais 

this.handleClick = this.handleClick.bind(this);

dans mon constructeur quand handleClick n'existait pas  

(Je l'avais effacé et j'avais accidentellement laissé la déclaration contraignante "ceci" dans mon constructeur).

Solution = supprimer la déclaration de liaison "this".

1
Sam Henderson

La solution que j'utilise pour ouvrir Popover pour les composants est reactstrap (composants React Bootstrap 4) .

    class Settings extends Component {
        constructor(props) {
            super(props);

            this.state = {
              popoversOpen: [] // array open popovers
            }
        }

        // toggle my popovers
        togglePopoverHelp = (selected) => (e) => {
            const index = this.state.popoversOpen.indexOf(selected);
            if (index < 0) {
              this.state.popoversOpen.Push(selected);
            } else {
              this.state.popoversOpen.splice(index, 1);
            }
            this.setState({ popoversOpen: [...this.state.popoversOpen] });
        }

        render() {
            <div id="settings">
                <button id="PopoverTimer" onClick={this.togglePopoverHelp(1)} className="btn btn-outline-danger" type="button">?</button>
                <Popover placement="left" isOpen={this.state.popoversOpen.includes(1)} target="PopoverTimer" toggle={this.togglePopoverHelp(1)}>
                  <PopoverHeader>Header popover</PopoverHeader>
                  <PopoverBody>Description popover</PopoverBody>
                </Popover>

                <button id="popoverRefresh" onClick={this.togglePopoverHelp(2)} className="btn btn-outline-danger" type="button">?</button>
                <Popover placement="left" isOpen={this.state.popoversOpen.includes(2)} target="popoverRefresh" toggle={this.togglePopoverHelp(2)}>
                  <PopoverHeader>Header popover 2</PopoverHeader>
                  <PopoverBody>Description popover2</PopoverBody>
                </Popover>
            </div>
        }
    }
0
dev-siberia

Ce même avertissement sera émis lors de tout changement d'état effectué lors d'un appel render().

Exemple de cas difficile à trouver: Lors du rendu d'un composant d'interface graphique à sélection multiple basé sur des données d'état, si state n'a rien à afficher, un appel à resetOptions() est considéré comme un changement d'état pour ce composant.

La solution évidente est de faire resetOptions() dans componentDidUpdate() au lieu de render().

0
Jari Turkia