Reactjs: ¿cómo modificar el estado del componente secundario dinámico o los accesorios del padre?

90

Básicamente, estoy tratando de hacer que las pestañas reaccionen, pero con algunos problemas.

Aquí está el archivo page.jsx

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

Al hacer clic en el botón A, las necesidades de componentes RadioGroup para deseleccionar el botón B .

"Seleccionado" solo significa un className de un estado o propiedad

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

});

La fuente de Button.jsxrealmente no importa, tiene un botón de radio HTML regular que activa el onChangeevento DOM nativo

El flujo esperado es:

  • Haga clic en el botón "A"
  • El botón "A" activa onChange, evento DOM nativo, que se propaga a RadioGroup
  • Se llama al oyente de RadioGroup onChange
  • RadioGroup necesita de-seleccione el botón B . Esta es mi pregunta.

Aquí está el problema principal que estoy encontrando: no puedo moverme <Button>aRadioGroup , porque la estructura de esto es tal que los niños son arbitrarios . Es decir, el marcado podría ser

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

o

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

Probé algunas cosas.

Intentar: En RadioGroupcontrolador onChange 's:

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

Intentar: En RadioGroupcontrolador onChange 's:

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

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

});

Problema: no pasa nada, incluso yo le doy Buttonun componentWillReceivePropsmétodo a la clase


Intento: intenté pasar algún estado específico del padre a los hijos, por lo que puedo actualizar el estado del padre y hacer que los hijos respondan automáticamente. En la función de renderizado de 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.

Mala solución n. ° 1 : use el método react-addons.js cloneWithProps para clonar a los niños en el tiempo de renderizado RadioGrouppara poder pasarles propiedades

Mala solución n. ° 2 : implementar una abstracción alrededor de HTML / JSX para que pueda pasar las propiedades dinámicamente (mátame):

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

Y luego, RadioGroupconstruya dinámicamente estos botones.

Esta pregunta no me ayuda porque necesito hacer que mis hijos sin saber qué son

Andy Ray
fuente
Si los hijos pueden ser arbitrarios, ¿cómo podría RadioGroupsaber que necesita reaccionar ante un evento de sus hijos arbitrarios? Necesariamente tiene que saber algo sobre sus hijos.
Ross Allen
1
En general, si desea modificar las propiedades de un componente que no es de su propiedad, clónelo usando React.addons.cloneWithPropsy pase los nuevos accesorios que le gustaría que tuviera. propsson inmutables, por lo que está creando un nuevo hash, fusionándolo con los accesorios actuales y pasando el nuevo hash propsa una nueva instancia del componente hijo.
Ross Allen

Respuestas:

42

No estoy seguro de por qué dice que usar cloneWithPropses una mala solución, pero aquí hay un ejemplo de trabajo que 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);

Aquí hay un jsFiddle que lo muestra en acción.

EDITAR : aquí hay un ejemplo más completo con contenido de pestaña dinámica: jsFiddle

Claude Précourt
fuente
15
Nota: cloneWithProps está en desuso. Utilice React.cloneElement en su lugar.
Chen-Tsu Lin
8
Esto es increíblemente complicado de hacer algo que D3 / Jquery podría hacer con solo un selector: no activado this. ¿Existe alguna ventaja real, además de haber usado React?
Estadísticas de aprendizaje por ejemplo
FYI ... esto no es "actualizar el estado de los niños desde el estado principal", es un niño que actualiza el estado principal para actualizar el estado de los niños. Aquí hay una diferencia en el verbage. En caso de que esto confunda a la gente.
sksallaj
1
@Incodeveritas tienes razón, la relación entre hermanos, padre-hijo e hijo-padre es un poco complicada. Esta es una forma modular de hacer las cosas. Pero tenga en cuenta que JQuery es un truco rápido para hacer las cosas, ReactJS mantiene las cosas de acuerdo con una orquestación.
sksallaj
18

Los botones deben ser apátridas. En lugar de actualizar las propiedades de un botón de forma explícita, simplemente actualice el estado del propio grupo y vuelva a renderizar. El método de renderizado del Grupo debería entonces mirar su estado al renderizar los botones y pasar "activo" (o algo) solo al botón activo.

Rygu
fuente
Para expandir, el botón onClick u otros métodos relevantes deben establecerse desde el padre para que cualquier acción vuelva a llamar al padre para establecer el estado allí, no en el botón en sí. De esa manera, el botón es solo una representación sin estado del estado actual de los padres.
David Mårtensson
6

Tal vez la mía sea una solución extraña, pero ¿por qué no usar el patrón de 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>);
    }

});

tal vez necesites algo más, pero esto me pareció muy poderoso,

Realmente prefiero usar un modelo externo que proporcione métodos de registro de observadores para varias tareas

Daniele Cruciani
fuente
¿No es de mala educación que un nodo llame explícitamente a setState en otro nodo? ¿Sería una forma más reactiva actualizar algún estado en el RadioGroup, activando otro pase de render en el que podría actualizar los accesorios de los descendientes según sea necesario?
qix
1
¿Explícitamente? No, el observador solo llama a una devolución de llamada, resulta ser el setState, pero de hecho podría haber especificado this.setState.bind (this) y ser exclusivamente bindend consigo mismo. Ok, si tuviera que ser honesto, debería haber definido una función / método local que llame a setState y lo haya registrado en el observador
Daniele Cruciani
1
¿Estoy leyendo esto mal, o tiene dos campos con el mismo nombre en un objeto para Button.jsx?
JohnK
Button.jsx incluye onChange()yonChange(e)
Josh
elimine el primero, es cortar y pegar de la pregunta (primera pregunta). No es el punto, este no es un código de trabajo, tampoco lo sería.
Daniele Cruciani
0

Cree un objeto que actúe como intermediario entre el padre y el hijo. Este objeto contiene referencias de funciones tanto en el padre como en el hijo. Luego, el objeto se pasa como un accesorio del padre al hijo. Ejemplo de código aquí:

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

AndroidDev
fuente