Reactjs: come modificare lo stato del componente figlio dinamico o gli oggetti di scena dal genitore?


91

Sto essenzialmente cercando di far reagire le schede, ma con alcuni problemi.

Ecco il file page.jsx

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

Quando si fa clic sul pulsante A, le esigenze dei componenti RadioGroup per deselezionare il pulsante B .

"Selezionato" significa semplicemente un className da uno stato o una proprietà

Ecco 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>);
    }

});

L'origine di Button.jsxnon ha molta importanza, ha un normale pulsante di opzione HTML che attiva l' onChangeevento DOM nativo

Il flusso previsto è:

  • Fare clic sul pulsante "A"
  • Il pulsante "A" attiva onChange, evento DOM nativo, che si sposta fino a RadioGroup
  • Viene chiamato il listener di RadioGroup onChange
  • RadioGroup ha bisogno di de-selezionare il pulsante B . Questa è la mia domanda.

Ecco il problema principale che sto incontrando: non posso trasferirmi <Button>inRadioGroup , perché la struttura di questo è tale che i bambini sono arbitrari . Cioè, il markup potrebbe essere

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

o

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

Ho provato alcune cose.

Tentativo: nel RadioGroupgestore 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 });

});

Problema:

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

Tentativo: nel RadioGroupgestore onChange:

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

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

});

Problema: non succede nulla, anche io do Buttonun componentWillReceivePropsmetodo alla classe


Tentativo: ho tentato di trasmettere uno stato specifico del genitore ai figli, quindi posso semplicemente aggiornare lo stato genitore e fare in modo che i bambini rispondano automaticamente. Nella funzione di rendering di RadioGroup:

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

Problema:

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.

Cattiva soluzione n. 1 : usa il metodo react-addons.js cloneWithProps per clonare i bambini in fase di rendering RadioGroupper poter passare loro le proprietà

Cattiva soluzione n. 2 : implementa un'astrazione attorno a HTML / JSX in modo che io possa passare dinamicamente le proprietà (uccidimi):

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

E poi RadioGroupcrea dinamicamente questi pulsanti.

Questa domanda non mi aiuta perché ho bisogno di rendere i miei figli senza sapere cosa sono


Se i bambini possono essere arbitrari, allora come potrebbero RadioGroupforse sapere che ha bisogno di reagire a un evento dei suoi figli arbitrari? Deve necessariamente sapere qualcosa sui suoi figli.
Ross Allen

1
In generale, se vuoi modificare le proprietà di un componente che non possiedi, clonalo usando React.addons.cloneWithPropse passa i nuovi oggetti di scena che vorresti che avesse. propssono immutabili, quindi stai creando un nuovo hash, fondendolo con gli oggetti di scena correnti e passando il nuovo hash propsa una nuova istanza del componente figlio.
Ross Allen

Risposte:


42

Non sono sicuro del motivo per cui dici che l'uso cloneWithPropsè una cattiva soluzione, ma ecco un esempio funzionante che lo usa.

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);

Ecco un jsFiddle che lo mostra in azione.

EDIT : ecco un esempio più completo con contenuto della scheda dinamica: jsFiddle


15
Nota: cloneWithProps è deprecato. Usa invece React.cloneElement.
Chen-Tsu Lin

8
È incredibilmente complicato fare qualcosa che D3 / Jquery potrebbe fare con un semplice selettore: not attivato this. C'è qualche vantaggio reale, oltre a usare React?
Statistiche di apprendimento per esempio

Cordiali saluti .. questo non è "aggiornare lo stato dei figli dallo stato genitore", questo è il figlio che aggiorna lo stato genitore per aggiornare lo stato dei figli. C'è una differenza nel verbale qui. Nel caso in cui questo confonda le persone.
sksallaj

1
@Incodeveritas hai ragione, la relazione tra fratelli, genitore-figlio e figlio-genitore è un po 'complicata. Questo è un modo modulare di fare le cose. Ma tieni presente che JQuery è un trucco rapido per fare le cose, ReactJS mantiene le cose secondo un'orchestrazione.
sksallaj

18

I pulsanti dovrebbero essere senza stato. Invece di aggiornare esplicitamente le proprietà di un pulsante, aggiorna semplicemente lo stato del gruppo e riesegui il rendering. Il metodo di rendering del gruppo dovrebbe quindi esaminare il suo stato durante il rendering dei pulsanti e passare "attivo" (o qualcosa del genere) solo al pulsante attivo.


Per espandere, il pulsante onClick o altri metodi pertinenti devono essere impostati dal genitore in modo che qualsiasi azione richiami il genitore per impostare lo stato lì, non nel pulsante stesso. In questo modo il pulsante è solo una rappresentazione senza stato dello stato corrente dei genitori.
David Mårtensson

6

Forse la mia è una soluzione strana, ma perché non usare il pattern dell'osservatore?

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>);
    }

});

forse hai bisogno di qualcos'altro, ma ho trovato questo molto potente,

Preferisco davvero utilizzare un modello esterno che fornisca metodi di registro dell'osservatore per varie attività


Non è una cattiva forma per un nodo chiamare esplicitamente setState su un altro nodo? Sarebbe un modo più reattivo aggiornare uno stato sul RadioGroup, attivando un altro passaggio di rendering in cui è possibile aggiornare gli oggetti di scena dei discendenti secondo necessità?
qix

1
Esplicitamente? No, l'osservatore chiama semplicemente un callback, sembra essere il setState, ma in realtà avrei potuto specificare this.setState.bind (this) e quindi essere vincolato esclusivamente a se stesso. Ok, se dovessi essere sincero avrei dovuto definire una funzione / metodo locale che chiamasse setState e registrarla nell'osservatore
Daniele Cruciani

1
Sto leggendo questo errore o hai due campi con lo stesso nome in un oggetto per Button.jsx?
JohnK

Button.jsx include onChange()eonChange(e)
Josh

rimuovi il primo, viene tagliato e incolla dalla domanda (prima domanda). Non è questo il punto, questo non è un codice funzionante, né lo sarebbe.
Daniele Cruciani

0

Crea un oggetto che funga da intermediario tra genitore e figlio. Questo oggetto contiene riferimenti a funzioni sia nel genitore che nel figlio. L'oggetto viene quindi passato come sostegno dal genitore al bambino. Esempio di codice qui:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.