¿Cómo actualizar el estado de los padres en React?

350

Mi estructura se ve de la siguiente manera:

Component 1  

 - |- Component 2


 - - |- Component 4


 - - -  |- Component 5  

Component 3

El componente 3 debería mostrar algunos datos dependiendo del estado del componente 5. Dado que los accesorios son inmutables, no puedo simplemente guardar su estado en el componente 1 y reenviarlo, ¿verdad? Y sí, he leído sobre redux, pero no quiero usarlo. Espero que sea posible resolverlo solo con reaccionar. ¿Me equivoco?

wklm
fuente
20
superfácil: pasar la función parent-setState via propiedad al componente hijo: <MyChildComponent setState = {p => {this.setState (p)}} /> En el componente hijo, llámelo a través de this.props. setState ({myObj, ...});
Marcel Ennix
@MarcelEnnix, Tu comentario me ahorra tiempo. Gracias.
Dinith Minura el
<MyChildComponent setState={(s,c)=>{this.setState(s, c)}} />Si va a utilizar este truco, asegúrese de admitir la devolución de llamada.
Barkermn01
44
Pasar una devolución de llamada para establecer el estado de los padres es una práctica realmente mala que podría generar problemas de mantenimiento. Rompe la encapsulación y hace que los componentes 2, 4 y 5 estén estrechamente acoplados a 1. Si recorre este camino, entonces no podrá reutilizar ninguno de estos componentes secundarios en otro lugar. Es mejor que tenga accesorios específicos para que los componentes secundarios puedan desencadenar eventos cada vez que ocurra algo, entonces el componente principal manejará ese evento correctamente.
Pato Loco
@MarcelEnnix, ¿por qué las llaves this.setState(p)? Lo intenté sin ellos y parece funcionar (soy muy nuevo en React)
Biganon

Respuestas:

678

Para la comunicación entre padres e hijos, debe pasar una función que establezca el estado de padres a hijos, de esta manera

class Parent extends React.Component {
  constructor(props) {
    super(props)

    this.handler = this.handler.bind(this)
  }

  handler() {
    this.setState({
      someVar: 'some value'
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {this.props.handler}/ >
  }
}

De esta manera, el niño puede actualizar el estado del padre con la llamada de una función pasada con accesorios.

Pero tendrá que repensar la estructura de sus componentes, porque, según tengo entendido, los componentes 5 y 3 no están relacionados.

Una posible solución es envolverlos en un componente de nivel superior que contendrá el estado de los componentes 1 y 3. Este componente establecerá el estado de nivel inferior a través de accesorios.

Ivan
fuente
66
¿por qué necesita this.handler = this.handler.bind (this) y no solo la función de controlador que establece el estado?
chemook78
35
@ chemook78 en ES6 Los métodos de clases React no se enlazan automáticamente a la clase. Entonces, si no agregamos el this.handler = this.handler.bind(this)constructor, thisdentro de la handlerfunción se hará referencia al cierre de la función, no a la clase. Si no desea vincular todas sus funciones en el constructor, hay dos formas más de lidiar con esto utilizando las funciones de flecha. Simplemente podría escribir el controlador de clic como onClick={()=> this.setState(...)}, o podría usar los inicializadores de propiedades junto con las funciones de flecha como se describe aquí babeljs.io/blog/2015/06/07/react-on-es6-plus en "Funciones de flecha"
Ivan
1
Aquí hay un ejemplo de esto en acción: plnkr.co/edit/tGWecotmktae8zjS5yEr?p=preview
Tamb
55
Todo eso tiene sentido, ¿por qué e.preventDefault? y eso requiere jquery?
Vincent Buscarello
1
Pregunta rápida, ¿esto no permite la asignación de un estado local dentro del niño?
ThisGuyCantEven
50

Encontré la siguiente solución de trabajo para pasar el argumento de la función Click del elemento secundario al componente primario:

Versión con pasar un método ()

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div><button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component {

    constructor(props) {
        super(props);
        var handleToUpdate  = this.handleToUpdate.bind(this);
        var arg1 = '';
    }

    handleToUpdate(someArg){
            alert('We pass argument from Child to Parent: ' + someArg);
            this.setState({arg1:someArg});
    }

    render() {
        var handleToUpdate  =   this.handleToUpdate;

        return (<div>
                    <ChildB handleToUpdate = {handleToUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Mira JSFIDDLE

Versión con pasar una función de flecha

//ChildB component
class ChildB extends React.Component {

    render() {

        var handleToUpdate  =   this.props.handleToUpdate;
        return (<div>
          <button onClick={() => handleToUpdate('someVar')}>
            Push me
          </button>
        </div>)
    }
}

//ParentA component
class ParentA extends React.Component { 
    constructor(props) {
        super(props);
    }

    handleToUpdate = (someArg) => {
            alert('We pass argument from Child to Parent: ' + someArg);
    }

    render() {
        return (<div>
            <ChildB handleToUpdate = {this.handleToUpdate} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentA />,
        document.querySelector("#demo")
    );
}

Mira JSFIDDLE

romano
fuente
¡Este es bueno! ¿Podrías explicar esta pieza? <ChildB handleToUpdate = {handleToUpdate.bind(this)} />¿Por qué tuve que atar de nuevo?
Dane
@Dane: debe vincular el contexto de esto para que sea el padre, de modo que cuando thisse llame dentro del niño, se thisrefiera al estado del padre y no al del niño. Este es el cierre en su máxima expresión!
Casey
@Casey, pero ¿no estamos haciendo eso en el constructor? ¿Y no es eso suficiente?
Dane
Tienes toda la razón! Me lo perdí. Sí, si ya lo hiciste en el constructor, ¡entonces estás listo!
Casey
Eres un compañero de leyenda! Esto mantendrá los componentes bien autocontenidos sin tener que ser forzados a crear componentes principales para manejar los intercambios estatales
adamj
13

Quiero agradecer a la respuesta más votada por darme la idea de mi propio problema, básicamente, la variación de la misma con la función de flecha y pasar el parámetro del componente secundario:

 class Parent extends React.Component {
  constructor(props) {
    super(props)
    // without bind, replaced by arrow func below
  }

  handler = (val) => {
    this.setState({
      someVar: val
    })
  }

  render() {
    return <Child handler = {this.handler} />
  }
}

class Child extends React.Component {
  render() {
    return <Button onClick = {() => this.props.handler('the passing value')}/ >
  }
}

Espero que ayude a alguien.

Ardhi
fuente
¿Qué tiene de especial la función de flecha sobre la llamada directa?
Ashish Kamble
@AshishKamble las thisfunciones de flecha en se refieren al contexto de los padres (es decir, la Parentclase).
CPHPython
Esta es una respuesta duplicada. Puede agregar un comentario a la respuesta aceptada y mencionar esta característica experimental para usar la función de flecha en la clase.
Arashsoft
10

Me gusta la respuesta con respecto a pasar funciones, es una técnica muy útil.

Por otro lado, también puede lograr esto usando pub / sub o usando una variante, un despachador, como lo hace Flux . La teoría es súper simple, haga que el componente 5 envíe un mensaje que el componente 3 está escuchando. El componente 3 luego actualiza su estado, lo que desencadena el renderizado. Esto requiere componentes con estado que, según su punto de vista, pueden ser o no un antipatrón. Estoy en contra de ellos personalmente y preferiría que algo más esté escuchando los despachos y cambie el estado desde arriba hacia abajo (Redux hace esto, pero agrega terminología adicional).

import { Dispatcher } from flux
import { Component } from React

const dispatcher = new Dispatcher()

// Component 3
// Some methods, such as constructor, omitted for brevity
class StatefulParent extends Component {
  state = {
    text: 'foo'
  } 

  componentDidMount() {
    dispatcher.register( dispatch => {
      if ( dispatch.type === 'change' ) {
        this.setState({ text: 'bar' })
      }
    }
  }

  render() {
    return <h1>{ this.state.text }</h1>
  }
}

// Click handler
const onClick = event => {
  dispatcher.dispatch({
    type: 'change'
  })
}

// Component 5 in your example
const StatelessChild = props => {
  return <button onClick={ onClick }>Click me</button> 
}

Los paquetes de despachador con Flux son muy simples, simplemente registra devoluciones de llamada e invoca cuando ocurre cualquier despacho, pasando por el contenido del despacho (en el ejemplo anterior no hay payloadcon el despacho, simplemente una identificación de mensaje). Puede adaptar esto al pub / sub tradicional (por ejemplo, usando EventEmitter de eventos, o alguna otra versión) muy fácilmente si eso tiene más sentido para usted.

Matt Styles
fuente
Los componentes de Mis reacciones están "ejecutándose" en el navegador como en un tutorial oficial ( facebook.github.io/react/docs/tutorial.html ) Intenté incluir Flux con browserify, pero el navegador dice que no se encuentra Dispatcher :(
wklm
2
La sintaxis que usé fue la sintaxis del módulo ES2016 que requiere transpilación (uso Babel , pero hay otros, la transformación de babelify se puede usar con browserify), se transpila avar Dispatcher = require( 'flux' ).Dispatcher
Matt Styles
8

Encontré la siguiente solución de trabajo para pasar el argumento de la función Click del elemento secundario al componente primario con param:

clase para padres:

class Parent extends React.Component {
constructor(props) {
    super(props)

    // Bind the this context to the handler function
    this.handler = this.handler.bind(this);

    // Set some state
    this.state = {
        messageShown: false
    };
}

// This method will be sent to the child component
handler(param1) {
console.log(param1);
    this.setState({
        messageShown: true
    });
}

// Render the child component and set the action property with the handler as value
render() {
    return <Child action={this.handler} />
}}

clase infantil:

class Child extends React.Component {
render() {
    return (
        <div>
            {/* The button will execute the handler function set by the parent component */}
            <Button onClick={this.props.action.bind(this,param1)} />
        </div>
    )
} }
H. parnian
fuente
2
¿Alguien puede decir si es una solución aceptable (particularmente interesado en pasar el parámetro como se sugiere).
ilans
param1solo se muestra en la consola, no se asigna, siempre se asignatrue
Ashish Kamble
No puedo hablar de la calidad de la solución, pero esto me pasa el parámetro con éxito.
James
6

Cuando necesite comunicarse entre el niño y el padre en cualquier nivel inferior, entonces es mejor hacer uso del contexto . En el componente principal, defina el contexto que puede invocar el elemento secundario, como

Componente principal en su caso componente 3

static childContextTypes = {
        parentMethod: React.PropTypes.func.isRequired
      };

       getChildContext() {
        return {
          parentMethod: (parameter_from_child) => this.parentMethod(parameter_from_child)
        };
      }

parentMethod(parameter_from_child){
// update the state with parameter_from_child
}

Ahora en el componente hijo (componente 5 en su caso), solo dígale a este componente que quiere usar el contexto de su padre.

 static contextTypes = {
       parentMethod: React.PropTypes.func.isRequired
     };
render(){
    return(
      <TouchableHighlight
        onPress={() =>this.context.parentMethod(new_state_value)}
         underlayColor='gray' >   

            <Text> update state in parent component </Text>              

      </TouchableHighlight>
)}

puedes encontrar el proyecto Demo en el repositorio

Rajan Twanabashu
fuente
No puedo entender esta respuesta, ¿pueden explicar más al respecto
Ashish Kamble
5

Parece que solo podemos pasar datos de padre a hijo a medida que reacciona promueve el Flujo de datos unidireccional, pero para hacer que el padre se actualice cuando algo sucede en su "componente hijo", generalmente usamos lo que se llama una "función de devolución de llamada".

Pasamos la función definida en el padre al hijo como "accesorios" y llamamos a esa función desde el hijo que la activa en el componente padre.


class Parent extends React.Component {
  handler = (Value_Passed_From_SubChild) => {
    console.log("Parent got triggered when a grandchild button was clicked");
    console.log("Parent->Child->SubChild");
    console.log(Value_Passed_From_SubChild);
  }
  render() {
    return <Child handler = {this.handler} />
  }
}
class Child extends React.Component {
  render() {
    return <SubChild handler = {this.props.handler}/ >
  }
}
class SubChild extends React.Component { 
  constructor(props){
   super(props);
   this.state = {
      somethingImp : [1,2,3,4]
   }
  }
  render() {
     return <button onClick = {this.props.handler(this.state.somethingImp)}>Clickme<button/>
  }
}
React.render(<Parent />,document.getElementById('app'));

 HTML
 ----
 <div id="app"></div>

En este ejemplo, podemos pasar datos desde SubChild -> Child -> Parent pasando la función a su hijo directo.

Vishal Bisht
fuente
4

He usado una respuesta de alta calificación de esta página muchas veces, pero mientras aprendía React, encontré una mejor manera de hacerlo, sin vinculación y sin función en línea dentro de los accesorios.

Solo mira aquí:

class Parent extends React.Component {

  constructor() {
    super();
    this.state={
      someVar: value
    }
  }

  handleChange=(someValue)=>{
    this.setState({someVar: someValue})
  }

  render() {
    return <Child handler={this.handleChange} />
  }

}

export const Child = ({handler}) => {
  return <Button onClick={handler} />
}

La clave está en una función de flecha:

handleChange=(someValue)=>{
  this.setState({someVar: someValue})
}

Puedes leer más aquí . Espero que esto sea útil para alguien =)

Александр Немков
fuente
Esto tiene mucho más sentido para mí. ¡Gracias!
Brett Rowberry
3

-Podemos crear ParentComponent y con el método handleInputChange para actualizar el estado ParentComponent. Importa el ChildComponent y pasamos dos accesorios del componente padre al hijo, es decir, la función handleInputChange y la cuenta.

import React, { Component } from 'react';
import ChildComponent from './ChildComponent';

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.state = {
      count: '',
    };
  }

  handleInputChange(e) {
    const { value, name } = e.target;
    this.setState({ [name]: value });
  }

  render() {
    const { count } = this.state;
    return (
      <ChildComponent count={count} handleInputChange={this.handleInputChange} />
    );
  }
}
  • Ahora creamos el archivo ChildComponent y lo guardamos como ChildComponent.jsx. Este componente no tiene estado porque el componente hijo no tiene un estado. Utilizamos la biblioteca de tipos de utilería para la verificación de tipos de utilería.

    import React from 'react';
    import { func, number } from 'prop-types';
    
    const ChildComponent = ({ handleInputChange, count }) => (
      <input onChange={handleInputChange} value={count} name="count" />
    );
    
    ChildComponent.propTypes = {
      count: number,
      handleInputChange: func.isRequired,
    };
    
    ChildComponent.defaultProps = {
      count: 0,
    };
    
    export default ChildComponent;
Sachin Metkari
fuente
¿Cómo funciona esto cuando el niño tiene un niño que afecta el apoyo de su padre?
Bobort el
3

Si este mismo escenario no se extiende a todas partes, puede usar el contexto de React, especialmente si no desea introducir todos los gastos generales que introducen las bibliotecas de administración de estado. Además, es más fácil de aprender. Pero tenga cuidado, podría usarlo en exceso y comenzar a escribir código incorrecto. Básicamente, usted define un componente Contenedor (que mantendrá y mantendrá esa parte del estado para usted) haciendo que todos los componentes interesados ​​en escribir / leer esa parte de sus datos sean sus elementos secundarios (no necesariamente elementos secundarios directos)

https://reactjs.org/docs/context.html

También puede usar React simple en su lugar.

<Component5 onSomethingHappenedIn5={this.props.doSomethingAbout5} />

pase doSomethingAbout5 hasta el Componente 1

    <Component1>
        <Component2 onSomethingHappenedIn5={somethingAbout5 => this.setState({somethingAbout5})}/>
        <Component5 propThatDependsOn5={this.state.somethingAbout5}/>
    <Component1/>

Si este es un problema común, debe comenzar a pensar en trasladar todo el estado de la aplicación a otro lugar. Tienes algunas opciones, las más comunes son:

https://redux.js.org/

https://facebook.github.io/flux/

Básicamente, en lugar de administrar el estado de la aplicación en su componente, envía comandos cuando sucede algo para actualizar el estado. Los componentes también extraen el estado de este contenedor para que todos los datos estén centralizados. Esto no significa que ya no pueda usar el estado local, pero ese es un tema más avanzado.

Pato Loco
fuente
2

entonces, si quieres actualizar el componente padre,

 class ParentComponent extends React.Component {
        constructor(props){
            super(props);
            this.state = {
               page:0
            }
        }

        handler(val){
            console.log(val) // 1
        }

        render(){
          return (
              <ChildComponent onChange={this.handler} />
           )
       }
   }


class ChildComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
             page:1
        };
    }

    someMethod = (page) => {
        this.setState({ page: page });
        this.props.onChange(page)
    }

    render() {
        return (
       <Button
            onClick={() => this.someMethod()} 
       > Click
        </Button>
      )
   }
}

Aquí onChange es un atributo con el método "controlador" vinculado a su instancia. Pasamos el manejador de métodos al componente de clase Child, para recibir a través de la propiedad onChange en su argumento props.

El atributo onChange se establecerá en un objeto de utilería como este:

props ={
onChange : this.handler
}

y pasó al componente hijo

Por lo tanto, el componente Child puede acceder al valor del nombre en el objeto props como este props.onChange

Se realiza mediante el uso de accesorios de renderizado.

Ahora el componente secundario tiene un botón "Click" con un evento onclick configurado para llamar al método del controlador que se le pasó a través de onChnge en su objeto de argumento de accesorios. Así que ahora this.props.onChange in Child contiene el método de salida en la clase principal Referencia y créditos: bits y piezas

Preetham NT
fuente
Lo sentimos ... por el retraso, aquí onChange es un atributo con el método "controlador" vinculado a su instancia. Pasamos el manejador de métodos al componente de clase Child, para recibir a través de la propiedad onChange en su argumento props. El atributo onChange se establecerá en un objeto props como este: props = {onChange: this.handler} y se pasará al componente hijo Para que el componente Child pueda acceder al valor del nombre en el objeto props como este props.onChange Se hace a través de El uso de accesorios de render. Referencia y créditos: [ blog.bitsrc.io/…
Preetham NT
0

Así es como lo hago.

type ParentProps = {}
type ParentState = { someValue: number }
class Parent extends React.Component<ParentProps, ParentState> {
    constructor(props: ParentProps) {
        super(props)
        this.state = { someValue: 0 }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, someValue: value})
    }

    render() {
        return <div>
            <Child changeFunction={this.handleChange} defaultValue={this.state.someValue} />
            <p>Value: {this.state.someValue}</p>
        </div>
    }
}

type ChildProps = { defaultValue: number, changeFunction: (value: number) => void}
type ChildState = { anotherValue: number }
class Child extends React.Component<ChildProps, ChildState> {
    constructor(props: ChildProps) {
        super(props)
        this.state = { anotherValue: this.props.defaultValue }

        this.handleChange = this.handleChange.bind(this)
    }

    handleChange(value: number) {
        this.setState({...this.state, anotherValue: value})
        this.props.changeFunction(value)
    }

    render() {
        return <div>
            <input onChange={event => this.handleChange(Number(event.target.value))} type='number' value={this.state.anotherValue}/>
        </div>
    }
}
YO SOY EL MEJOR
fuente
0

La mayoría de las respuestas dadas anteriormente son para diseños basados ​​en React.Component. Si está utilizando useStatelas actualizaciones recientes de la biblioteca React, siga esta respuesta

leeCoder
fuente
-3
<Footer 
  action={()=>this.setState({showChart: true})}
/>

<footer className="row">
    <button type="button" onClick={this.props.action}>Edit</button>
  {console.log(this.props)}
</footer>

Try this example to write inline setState, it avoids creating another function.
suresh
fuente
Causa problemas de rendimiento: stackoverflow.com/q/36677733/3328979
Arashsoft