Reactjs: como modificar o estado do componente filho dinâmico ou props do pai?

89

Estou essencialmente tentando fazer as guias reagirem, mas com alguns problemas.

Aqui está o arquivo page.jsx

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

Quando você clica no botão A, as necessidades de componentes RadioGroup para de-selecionar o botão B .

"Selecionado" significa apenas um className de um estado ou propriedade

Aqui está 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>);
    }

});

A fonte de Button.jsxrealmente não importa, ele tem um botão de opção HTML regular que aciona o onChangeevento DOM nativo

O fluxo esperado é:

  • Clique no botão "A"
  • O botão "A" aciona onChange, evento DOM nativo, que borbulha para RadioGroup
  • O ouvinte RadioGroup onChange é chamado
  • RadioGroup precisa de-selecione o botão B . Esta é a minha pergunta.

Aqui está o principal problema que estou encontrando: não consigo <Button>entrarRadioGroup , porque a estrutura disso é que os filhos são arbitrários . Ou seja, a marcação pode ser

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

ou

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

Tentei algumas coisas.

Tentativa:RadioGroup manipulador onChange de In :

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(...)

Tentativa:RadioGroup manipulador onChange de In :

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

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

});

Problema: nada acontece, até eu dou Buttonum componentWillReceivePropsmétodo para a classe


Tentativa: tentei passar algum estado específico do pai para os filhos, então posso apenas atualizar o estado dos pais e fazer com que os filhos respondam automaticamente. Na função render do 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.

Solução ruim nº 1 : use o método react-addons.js cloneWithProps para clonar os filhos no tempo de renderização RadioGrouppara poder passar propriedades para eles

Solução ruim nº 2 : implemente uma abstração em torno de HTML / JSX para que eu possa passar as propriedades dinamicamente (me mate):

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

Em seguida, RadioGroupcrie esses botões dinamicamente.

Esta pergunta não me ajuda porque preciso renderizar meus filhos sem saber o que são

Andy Ray
fonte
Se os filhos podem ser arbitrários, como poderiam RadioGroupsaber que precisam reagir a um evento de seus filhos arbitrários? Ele necessariamente tem que saber algo sobre seus filhos.
Ross Allen de
1
Em geral, se você quiser modificar as propriedades de um componente que não possui, clone-o usando React.addons.cloneWithPropse passe os novos adereços que gostaria que ele tivesse. propssão imutáveis, então você está criando um novo hash, mesclando-o com os adereços atuais e passando o novo hash propspara uma nova instância do componente filho.
Ross Allen de

Respostas:

41

Não sei por que você diz que usar cloneWithPropsé uma solução ruim, mas aqui está um exemplo prático de como usá-lo.

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

Aqui está um jsFiddle mostrando em ação.

EDIT : aqui está um exemplo mais completo com conteúdo de guia dinâmico: jsFiddle

Claude Précourt
fonte
15
Nota: cloneWithProps está obsoleto. Use React.cloneElement em vez disso.
Chen-Tsu Lin
8
Isso é incrivelmente complicado de fazer algo que D3 / Jquery poderia fazer com apenas um seletor: not ativado this. Existe alguma vantagem real, além de ter usado React?
Estatísticas de aprendizagem por exemplo
Para sua informação, isso não é "atualizar o estado dos filhos a partir do estado dos pais", é a atualização dos filhos do estado dos pais para atualizar o estado dos filhos. Há uma diferença no verbage aqui. Caso isso confunda as pessoas.
sksallaj
1
@Incodeveritas você está certo, relacionamento entre irmãos, pai-filho e filho-pai é um pouco complicado. Esta é uma maneira modular de fazer as coisas. Mas tenha em mente que JQuery é um hack rápido para fazer as coisas, ReactJS mantém as coisas de acordo com uma orquestração.
sksallaj
18

Os botões devem ser sem estado. Em vez de atualizar as propriedades de um botão explicitamente, apenas atualize o próprio estado do Grupo e renderize novamente. O método de renderização do Grupo deve então observar seu estado ao renderizar os botões e passar "ativo" (ou algo assim) apenas para o botão ativo.

Rygu
fonte
Para expandir, o botão onClick ou outros métodos relevantes devem ser definidos no pai para que qualquer ação retorne ao pai para definir o estado lá, não no próprio botão. Dessa forma, o botão é apenas uma representação sem estado do estado atual dos pais.
David Mårtensson
6

Talvez a minha seja uma solução estranha, mas por que não usar o padrão do observador?

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

});

talvez você precise de outra coisa, mas eu achei isso muito poderoso,

Eu realmente prefiro usar um modelo externo que forneça métodos de registro de observador para várias tarefas

Daniele Cruciani
fonte
Não é uma má forma para um nó chamar explicitamente setState em outro nó? Uma maneira mais reativa seria atualizar algum estado no RadioGroup, disparando outra passagem de renderização na qual você poderia atualizar os adereços dos descendentes conforme necessário?
qix
1
Explicitamente? Não, o observador apenas chama um retorno de chamada, por acaso é o setState, mas na verdade eu poderia ter especificado this.setState.bind (this) e, portanto, ser exclusivamente bindend a si mesmo. Ok, se eu tivesse que ser honesto, deveria ter definido uma função / método local que chama setState e registrado no observador
Daniele Cruciani
1
Estou lendo errado ou você tem dois campos com o mesmo nome em um objeto para Button.jsx?
JohnK
Button.jsx inclui onChange()eonChange(e)
Josh
remova o primeiro, ele é cortado e colado da pergunta (primeira pergunta). Não é esse o ponto, este não é um código funcional, nem seria.
Daniele Cruciani
0

Crie um objeto que atue como um intermediário entre o pai e o filho. Este objeto contém referências de função no pai e no filho. O objeto é então passado como um suporte do pai para o filho. Exemplo de código aqui:

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

AndroidDev
fonte