Reactjs: comment modifier l'état du composant enfant dynamique ou les accessoires du parent?

89

J'essaie essentiellement de faire réagir des onglets, mais avec quelques problèmes.

Voici le fichier page.jsx

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

Lorsque vous cliquez sur le bouton A, les besoins en composants RadioGroup à Désélectionnez bouton B .

"Sélectionné" signifie simplement un nom de classe d'un état ou d'une propriété

Voici RadioGroup.jsx:

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },

    render: function() {
        return (<div onChange={this.onChange}>
            {this.props.children}
        </div>);
    }

});

La source de Button.jsxn'a pas vraiment d'importance, elle a un bouton radio HTML normal qui déclenche l' onChangeévénement DOM natif

Le débit attendu est:

  • Cliquez sur le bouton "A"
  • Le bouton "A" déclenche onChange, un événement DOM natif, qui se propage jusqu'à RadioGroup
  • L'écouteur RadioGroup onChange est appelé
  • RadioGroup doit bouton de sélection-B . Telle est ma question.

Voici le problème principal que je rencontre: je ne peux pas <Button>entrerRadioGroup , car la structure de ceci est telle que les enfants sont arbitraires . Autrement dit, le balisage pourrait être

<RadioGroup>
    <Button title="A" />
    <Button title="B" />
</RadioGroup>

ou

<RadioGroup>
    <OtherThing title="A" />
    <OtherThing title="B" />
</RadioGroup>

J'ai essayé plusieurs choses.

Tentative: dans RadioGrouple gestionnaire onChange:

React.Children.forEach( this.props.children, function( child ) {

    // Set the selected state of each child to be if the underlying <input>
    // value matches the child's value

    child.setState({ selected: child.props.value === e.target.value });

});

Problème:

Invalid access to component property "setState" on exports at the top
level. See react-warning-descriptors . Use a static method
instead: <exports />.type.setState(...)

Tentative: dans RadioGrouple gestionnaire onChange:

React.Children.forEach( this.props.children, function( child ) {

    child.props.selected = child.props.value === e.target.value;

});

Problème: rien ne se passe, même je donne Buttonune componentWillReceivePropsméthode à la classe


Tentative: j'ai essayé de transmettre un état spécifique du parent aux enfants, afin que je puisse simplement mettre à jour l'état parent et que les enfants répondent automatiquement. Dans la fonction de rendu de RadioGroup:

React.Children.forEach( this.props.children, function( item ) {
    this.transferPropsTo( item );
}, this);

Problème:

Failed to make request: Error: Invariant Violation: exports: You can't call
transferPropsTo() on a component that you don't own, exports. This usually
means you are calling transferPropsTo() on a component passed in as props
or children.

Mauvaise solution n ° 1 : utilisez la méthode react-addons.js cloneWithProps pour cloner les enfants au moment du rendu RadioGroupafin de pouvoir leur transmettre des propriétés

Mauvaise solution n ° 2 : implémenter une abstraction autour de HTML / JSX pour que je puisse passer les propriétés dynamiquement (tuez-moi):

<RadioGroup items=[
    { type: Button, title: 'A' },
    { type: Button, title: 'B' }
]; />

Et puis en RadioGroupconstruisez dynamiquement ces boutons.

Cette question ne m'aide pas car j'ai besoin de rendre mes enfants sans savoir ce qu'ils sont

Andy Ray
la source
Si les enfants peuvent être arbitraires, alors comment pourraient RadioGroup-ils savoir dont ils ont besoin pour réagir à un événement de ses enfants arbitraires? Il doit nécessairement savoir quelque chose sur ses enfants.
Ross Allen
1
En général, si vous souhaitez modifier les propriétés d'un composant que vous ne possédez pas, clonez-le en utilisant React.addons.cloneWithPropset transmettez les nouveaux accessoires que vous souhaitez qu'il ait. propssont immuables, vous créez donc un nouveau hachage, le fusionnez avec les accessoires actuels et vous transmettez le nouveau hachage propsà une nouvelle instance du composant enfant.
Ross Allen

Réponses:

41

Je ne sais pas pourquoi vous dites qu'utiliser cloneWithProps est une mauvaise solution, mais voici un exemple concret de son utilisation.

var Hello = React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});

var App = React.createClass({
    render: function() {
        return (
            <Group ref="buttonGroup">
                <Button key={1} name="Component A"/>
                <Button key={2} name="Component B"/>
                <Button key={3} name="Component C"/>
            </Group>
        );
    }
});

var Group = React.createClass({
    getInitialState: function() {
        return {
            selectedItem: null
        };
    },

    selectItem: function(item) {
        this.setState({
            selectedItem: item
        });
    },

    render: function() {
        var selectedKey = (this.state.selectedItem && this.state.selectedItem.props.key) || null;
        var children = this.props.children.map(function(item, i) {
            var isSelected = item.props.key === selectedKey;
            return React.addons.cloneWithProps(item, {
                isSelected: isSelected,
                selectItem: this.selectItem,
                key: item.props.key
            });
        }, this);

        return (
            <div>
                <strong>Selected:</strong> {this.state.selectedItem ? this.state.selectedItem.props.name : 'None'}
                <hr/>
                {children}
            </div>
        );
    }

});

var Button = React.createClass({
    handleClick: function() {
        this.props.selectItem(this);
    },

    render: function() {
        var selected = this.props.isSelected;
        return (
            <div
                onClick={this.handleClick}
                className={selected ? "selected" : ""}
            >
                {this.props.name} ({this.props.key}) {selected ? "<---" : ""}
            </div>
        );
    }

});


React.renderComponent(<App />, document.body);

Voici un jsFiddle le montrant en action.

EDIT : voici un exemple plus complet avec un contenu d'onglet dynamique: jsFiddle

Claude Précourt
la source
15
Remarque: cloneWithProps est obsolète. Utilisez plutôt React.cloneElement.
Chen-Tsu Lin
8
C'est incroyablement compliqué de faire quelque chose que D3 / Jquery pourrait faire avec juste un sélecteur: not on this. Y a-t-il un réel avantage, à part avoir utilisé React?
Apprentissage des statistiques par exemple le
FYI .. ce n'est pas "mettre à jour l'état des enfants à partir de l'état parent", c'est l'enfant qui met à jour l'état parent afin de mettre à jour l'état des enfants. Il y a une différence dans le verbage ici. Au cas où cela dérouterait les gens.
sksallaj
1
@Incodeveritas vous avez raison, la relation fraternelle, parent-enfant et enfant-parent est un peu compliquée. C'est une manière modulaire de faire les choses. Mais gardez à l'esprit que JQuery est un hack rapide pour faire avancer les choses, ReactJS garde les choses selon une orchestration.
sksallaj
18

Les boutons doivent être sans état. Au lieu de mettre à jour les propriétés d'un bouton explicitement, mettez simplement à jour l'état du groupe et effectuez un nouveau rendu. La méthode de rendu du groupe devrait alors regarder son état lors du rendu des boutons et passer "actif" (ou quelque chose) uniquement au bouton actif.

Rygu
la source
Pour développer, le bouton onClick ou d'autres méthodes pertinentes doivent être définis à partir du parent afin que toute action rappelle au parent pour définir l'état là-bas, pas dans le bouton lui-même. De cette façon, le bouton n'est qu'une représentation sans état de l'état actuel des parents.
David Mårtensson
6

Peut-être que la mienne est une solution étrange, mais pourquoi ne pas utiliser de modèle d'observateur?

RadioGroup.jsx

module.exports = React.createClass({
buttonSetters: [],
regSetter: function(v){
   buttonSetters.push(v);
},
handleChange: function(e) {
   // ...
   var name = e.target.name; //or name
   this.buttonSetters.forEach(function(v){
      if(v.name != name) v.setState(false);
   });
},
render: function() {
  return (
    <div>
      <Button title="A" regSetter={this.regSetter} onChange={handleChange}/>
      <Button title="B" regSetter={this.regSetter} onChange={handleChange} />
    </div>
  );
});

Button.jsx

module.exports = React.createClass({

    onChange: function( e ) {
        // How to modify children properties here???
    },
    componentDidMount: function() {
         this.props.regSetter({name:this.props.title,setState:this.setState});
    },
    onChange:function() {
         this.props.onChange();
    },
    render: function() {
        return (<div onChange={this.onChange}>
            <input element .../>
        </div>);
    }

});

peut-être avez-vous besoin d'autre chose, mais j'ai trouvé cela très puissant,

Je préfère vraiment utiliser un modèle externe qui fournit des méthodes de registre d'observateurs pour diverses tâches

Daniele Cruciani
la source
N'est-ce pas une mauvaise forme pour un nœud d'appeler explicitement setState sur un autre nœud? Une manière plus réactive serait de mettre à jour un état sur le RadioGroup, déclenchant une autre passe de rendu dans laquelle vous pourriez mettre à jour les accessoires des descendants si nécessaire?
qix
1
Explicitement? Non, l'observateur appelle simplement un callback, il se trouve que c'est le setState, mais en fait j'aurais pu spécifier this.setState.bind (this) et donc être exclusivement lié à lui-même. Ok, si je devais être honnête, j'aurais dû définir une fonction / méthode locale qui appelle setState et l'enregistrer dans l'observateur
Daniele Cruciani
1
Est-ce que je lis mal ou avez-vous deux champs avec le même nom dans un objet pour Button.jsx?
JohnK
Button.jsx comprend onChange()etonChange(e)
Josh
supprimer le premier, il est copié-collé de la question (première question). Ce n'est pas le but, ce n'est pas un code fonctionnel, ce ne serait pas non plus.
Daniele Cruciani
0

Créez un objet qui agit comme intermédiaire entre le parent et l'enfant. Cet objet contient des références de fonction dans le parent et l'enfant. L'objet est ensuite passé comme accessoire du parent à l'enfant. Exemple de code ici:

https://stackoverflow.com/a/61674406/753632

AndroidDev
la source