Llamar al método secundario del padre

699

Tengo dos componentes:

  1. Componente padre
  2. Componente hijo

Estaba tratando de llamar al método Child desde Parent, lo intenté de esta manera pero no pude obtener un resultado:

class Parent extends Component {
  render() {
    return (
      <Child>
        <button onClick={Child.getAlert()}>Click</button>
      </Child>
      );
    }
  }

class Child extends Component {
  getAlert() {
    alert('clicked');
  }
 
  render() {
    return (
      <h1 ref="hello">Hello</h1>
    );
  }
}

¿Hay alguna forma de llamar al método Child desde Parent?

Nota: Los componentes secundarios y principales se encuentran en dos archivos diferentes.

1
1031

En primer lugar, permítanme expresar que, en general, esta no es la forma de hacer las cosas en React Land. Por lo general, lo que desea hacer es transmitir la funcionalidad a los niños en accesorios y dejar pasar las notificaciones de los niños en los eventos (o mejor aún:) dispatch.

Pero si debe exponer un método imperativo en un componente secundario, puede usar refs . Recuerde que esta es una trampilla de escape y, por lo general, indica que hay un mejor diseño disponible.

Previously, refs were only supported for Class-based components. With the advent of React Hooks, that's no longer the case

Reacción moderna con ganchos ( v16.8+)

const { forwardRef, useRef, useImperativeHandle } = React;

// We need to wrap component in `forwardRef` in order to gain
// access to the ref object that is assigned using the `ref` prop.
// This ref is passed as the second parameter to the function component.
const Child = forwardRef((props, ref) => {

  // The component instance will be extended
  // with whatever you return from the callback passed
  // as the second argument
  useImperativeHandle(ref, () => ({

    getAlert() {
      alert("getAlert from Child");
    }

  }));

  return <h1>Hi</h1>;
});

const Parent = () => {
  // In order to gain access to the child component instance,
  // you need to assign it to a `ref`, so we call `useRef()` to get one
  const childRef = useRef();

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.getAlert()}>Click</button>
    </div>
  );
};

ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

La documentación para useImperativeHandle()está aquí :

useImperativeHandle customizes the instance value that is exposed to parent components when using ref.

API heredada con componentes de clase ( >= [email protected])

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('getAlert from Child');
  }

  render() {
    return <h1>Hello</h1>;
  }
}

ReactDOM.render(<Parent />, document.getElementById('root'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

API de referencia de devolución de llamada

Las referencias de estilo de devolución de llamada son otro enfoque para lograr esto, aunque no son tan comunes en React moderno:

const { Component } = React;
const { render } = ReactDOM;

class Parent extends Component {
  render() {
    return (
      <div>
        <Child ref={instance => { this.child = instance; }} />
        <button onClick={() => { this.child.getAlert(); }}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('clicked');
  }

  render() {
    return (
      <h1>Hello</h1>
    );
  }
}


render(
  <Parent />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>
40
  • 31
    Estoy cansado, pero termino con este error "_this2.refs.child.getAlert no es una función"N8FURY 21/06/2016 a las 17:58
  • 25
    Eso es porque connectdevuelve un componente de orden superior que envuelve su instancia original. Tendrá que llamar getWrappedInstance()en el componente conectado para obtener su primera componente original. Entonces puedes llamar a métodos de instancia sobre eso. rossipedia 25/12/2016 a las 19:07
  • 22
    Este no es realmente un buen patrón. Sin mencionar que los árbitros de cuerdas están mal vistos. Es mejor pasar los accesorios al componente secundario y luego hacer que un botón haga clic en el padre para cambiar el estado del padre y pasar un elemento de estado al niño que activará al niño componentWillReceivePropsy lo usará como un disparador. ffxsam 30/03/2017 a las 16:41
  • 11
    No, por lo general no es el mejor patrón, es más una trampilla de escape cuando la necesita y debe usarse solo en emergencias. Además, esta respuesta se escribió cuando todavía había referencias de cadenas, y tiene razón en que no son la forma "correcta" de hacer las cosas en estos días. rossipedia 30 de marzo de 2017 a las 17:58
  • 73
    Si la mejor práctica es crear un laberinto de lógica para hacer algo tan simple como llamar al método de un componente secundario, entonces no estoy de acuerdo con las mejores prácticas. aaaaaa 3 dic. 18 a las 17:47
190

Puedes usar otro patrón aquí:

class Parent extends Component {
 render() {
  return (
    <div>
      <Child setClick={click => this.clickChild = click}/>
      <button onClick={() => this.clickChild()}>Click</button>
    </div>
  );
 }
}

class Child extends Component {
 constructor(props) {
    super(props);
    this.getAlert = this.getAlert.bind(this);
 }
 componentDidMount() {
    this.props.setClick(this.getAlert);
 }
 getAlert() {
    alert('clicked');
 }
 render() {
  return (
    <h1 ref="hello">Hello</h1>
  );
 }
}

Lo que hace es establecer el clickChildmétodo del padre cuando se monta el hijo. De esta manera, cuando haga clic en el botón de padre, se llamará al clickChildque llama al niño getAlert.

Esto también funciona si su hijo está envuelto, por connect()lo que no necesita el getWrappedInstance()truco.

Tenga en cuenta que no puede usar onClick={this.clickChild}en padre porque cuando se representa el padre, el hijo no está montado, por this.clickChildlo que aún no está asignado. El uso onClick={() => this.clickChild()}está bien porque cuando hace clic en el botón this.clickChildya debería estar asignado.

17
72

Método alternativo con useEffect:

Padre:

const [refresh, doRefresh] = useState(0);
<Button onClick={() => doRefresh(prev => prev + 1)} />
<Children refresh={refresh} />

Niños:

useEffect(() => {
    performRefresh(); //children function of interest
  }, [props.refresh]);
4
  • 1
    PD. Si su deseo es simplemente volver a renderizar el formulario (por ejemplo, para restablecer los campos de entrada), entonces ni siquiera necesita incluir el useEffect, puede hacer que el accesorio que se envía al componente cambieMatt Fletcher 8/06/20 a las 11:07
  • @tonymayoral ¿Hay alguna forma en que podamos usar useState dentro del componente Child y usar doRefresh de Parent. En mi caso, no quiero que mi padre vuelva a renderizar. Neel Dsouza 26 oct.20 a las 7:09
  • ¡Esta solución funciona muy bien para actualizar o llamar a una función en varios niños a la vez! Blake Allen 8 de abril a las 3:04
  • @MattFletcher sin useEffect, puede recibir bucles infinitoshik hyper 23 de abril a las 2:39
32

https://facebook.github.io/react/tips/expose-component-functions.html para obtener más respuestas, consulte aquí Métodos de llamada en los componentes secundarios de React

Al mirar las referencias del componente "razón", está rompiendo la encapsulación y haciendo imposible refactorizar ese componente sin examinar cuidadosamente todos los lugares en los que se usa. Debido a esto, recomendamos encarecidamente tratar las referencias como privadas para un componente, al igual que el estado.

En general, los datos deben transmitirse por el árbol a través de accesorios. Hay algunas excepciones a esto (como llamar a .focus () o activar una animación única que realmente no "cambia" el estado), pero cada vez que exponga un método llamado "set", los accesorios suelen ser una mejor elección. Intente que el componente de entrada interno se preocupe por su tamaño y apariencia, de modo que ninguno de sus antepasados ​​lo haga.

2
  • 8
    Aquí está la fuente de esta respuesta: discusion.reactjs.org/t/… . No hay problema con citar a otros, pero al menos poner alguna referencia. Jodo 11/10/2017 a las 7:46
  • 6
    ¿Cómo rompe exactamente esto la encapsulación más que los accesorios? Timmmm 31/01/20 a las 16:47
13

Espero no estar repitiendo nada de lo anterior, pero ¿qué pasa con pasar un apoyo de devolución de llamada que establece la función en el padre? Esto funciona y es bastante fácil. (El código agregado está entre los ////)

class Parent extends Component {
  ///// 
  getAlert = () => {} // initial value for getAlert

  setGetAlertMethod = (newMethod) => {
    this.getAlert = newMethod;
  }
  /////

  render() {
    return (
      <Child setGetAlertMethod={this.setGetAlertMethod}>
        <button onClick={this.getAlert}>Click</button>
      </Child>
      );
    }
  }



class Child extends Component {
  /////
  componentDidMount() {
    this.props.setGetAlertMethod(this.getAlert);
  }
  /////

  getAlert() => {
    alert('clicked');
  }

  render() {
    return (
      <h1 ref="hello">Hello</h1>
    );
  }
}
12

No estaba satisfecho con ninguna de las soluciones presentadas aquí. En realidad, existe una solución muy simple que se puede hacer usando Javascript puro sin depender de alguna funcionalidad de React que no sea el objeto de accesorios básico, y le brinda el beneficio de comunicarse en cualquier dirección (padre -> hijo, hijo -> padre). Debe pasar un objeto del componente principal al componente secundario. Este objeto es lo que yo llamo una "referencia bidireccional" o biRef para abreviar. Básicamente, el objeto contiene una referencia a métodos en el padre que el padre quiere exponer. Y el componente hijo adjunta métodos al objeto que el padre puede llamar. Algo como esto:

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      someParentFunction: someParentFunction
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef.someChildFunction = someChildFunction;

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

La otra ventaja de esta solución es que puede agregar muchas más funciones en el padre y el hijo mientras las pasa del padre al hijo utilizando una sola propiedad.

Una mejora con respecto al código anterior es no agregar las funciones principal y secundaria directamente al objeto biRef, sino a los submiembros. Las funciones principales deben agregarse a un miembro llamado "principal", mientras que las funciones secundarias deben agregarse a un miembro llamado "secundario".

// Parent component.
function MyParentComponent(props) {

   function someParentFunction() {
      // The child component can call this function.
   }

   function onButtonClick() {
       // Call the function inside the child component.
       biRef.child.someChildFunction();
   }

   // Add all the functions here that the child can call.
   var biRef = {
      parent: {
          someParentFunction: someParentFunction
      }
   }

   return <div>
       <MyChildComponent biRef={biRef} />
       <Button onClick={onButtonClick} />
   </div>;
}


// Child component
function MyChildComponent(props) {

   function someChildFunction() {
      // The parent component can call this function.
   }


   function onButtonClick() {
      // Call the parent function.
      props.biRef.parent.someParentFunction();
   }

   // Add all the child functions to props.biRef that you want the parent
   // to be able to call.
   props.biRef {
       child: {
            someChildFunction: someChildFunction
       }
   }

   return <div>
       <Button onClick={onButtonClick} />
   </div>;
}

Al colocar las funciones padre e hijo en miembros separados del objeto biRef, tendrá una separación clara entre los dos y podrá ver fácilmente cuáles pertenecen al padre o al hijo. También ayuda a evitar que un componente secundario sobrescriba accidentalmente una función principal si la misma función aparece en ambos.

Una última cosa es que si observa, el componente principal crea el objeto biRef con var mientras que el componente secundario accede a él a través del objeto props. Puede ser tentador no definir el objeto biRef en el padre y acceder a él desde su padre a través de su propio parámetro props (que podría ser el caso en una jerarquía de elementos de la interfaz de usuario). Esto es arriesgado porque el niño puede pensar que una función que está llamando al padre pertenece al padre cuando en realidad podría pertenecer a un abuelo. No hay nada de malo en esto siempre que lo sepa. A menos que tenga una razón para apoyar alguna jerarquía más allá de una relación padre / hijo, es mejor crear el biRef en su componente padre.

1
  • esto funciona muy bien, pero ¿va en contra de la mentalidad de reacción? gaitat 9 mar a las 0:30
10

Podemos usar referencias de otra manera como-

Vamos a crear un elemento Padre, renderizará un <Child/>componente. Como puede ver, el componente que se renderizará, debe agregar el atributo ref y proporcionarle un nombre.
Luego, la triggerChildAlertfunción, ubicada en la clase padre, accederá a la propiedad refs del contexto this (cuando triggerChildAlertse active la función accederá a la referencia secundaria y tendrá todas las funciones del elemento hijo).

class Parent extends React.Component {
    triggerChildAlert(){
        this.refs.child.callChildMethod();
        // to get child parent returned  value-
        // this.value = this.refs.child.callChildMethod();
        // alert('Returned value- '+this.value);
    }

    render() {
        return (
            <div>
                {/* Note that you need to give a value to the ref parameter, in this case child*/}
                <Child ref="child" />
                <button onClick={this.triggerChildAlert}>Click</button>
            </div>
        );
    }
}  

Ahora, el componente hijo, como se diseñó teóricamente anteriormente, se verá así:

class Child extends React.Component {
    callChildMethod() {
        alert('Hello World');
        // to return some value
        // return this.state.someValue;
    }

    render() {
        return (
            <h1>Hello</h1>
        );
    }
}

Aquí está el código fuente: ¡
Hope te ayudará!

1
9

puede usar refpara llamar a la función del componente secundario desde el elemento primario

Solución de componente funcional

en el componente funcional, debe usar useImperativeHandlepara obtener una referencia en un niño como se muestra a continuación

import React, { forwardRef, useRef, useImperativeHandle } from 'react';
export default function ParentFunction() {
    const childRef = useRef();
    return (
        <div className="container">
            <div>
                Parent Component
            </div>
            <button
                onClick={() => { childRef.current.showAlert() }}
            >
            Call Function
            </button>
            <Child ref={childRef}/>
        </div>
    )
}
const Child = forwardRef((props, ref) => {
    useImperativeHandle(
        ref,
        () => ({
            showAlert() {
                alert("Child Function Called")
            }
        }),
    )
    return (
       <div>Child Component</div>
    )
})

Solución de componente de clase

Child.js

import s from './Child.css';

class Child extends Component {
 getAlert() {
    alert('clicked');
 }
 render() {
  return (
    <h1>Hello</h1>
  );
 }
}

export default Child;

Parent.js

class Parent extends Component {
 render() {
  onClick() {
    this.refs.child.getAlert();
  }
  return (
    <div>
      <Child ref="child" />
      <button onClick={this.onClick}>Click</button>
    </div>
  );
 }
}
1
8

Si está haciendo esto simplemente porque desea que Child proporcione un rasgo reutilizable a sus padres, entonces podría considerar hacerlo usando render-props en su lugar.

Esa técnica en realidad da la vuelta a la estructura. El Childahora envuelve al padre, así que lo renombré a AlertTraitcontinuación. Mantuve el nombre Parentde continuidad, aunque ahora no es realmente un padre.

// Use it like this:

  <AlertTrait renderComponent={Parent}/>


class AlertTrait extends Component {
  // You will need to bind this function, if it uses 'this'
  doAlert() {
    alert('clicked');
  }
  render() {
    return this.props.renderComponent({ doAlert: this.doAlert });
  }
}

class Parent extends Component {
  render() {
    return (
      <button onClick={this.props.doAlert}>Click</button>
    );
  }
}

En este caso, el AlertTrait proporciona uno o más rasgos que transmite como accesorios a cualquier componente que se le haya dado en su renderComponentaccesorio.

El padre recibe doAlertcomo apoyo y puede llamarlo cuando sea necesario.

(Para mayor claridad, llamé al accesorio renderComponenten el ejemplo anterior. Pero en los documentos de React vinculados anteriormente, simplemente lo llaman render).

El componente Trait puede renderizar cosas que rodean al padre, en su función de renderizado, pero no renderiza nada dentro del padre. En realidad, podría renderizar cosas dentro del Padre, si pasa otro accesorio (por ejemplo renderChild) al padre, que el padre podría usar durante su método de renderizado.

Esto es algo diferente de lo que pidió el OP, pero algunas personas podrían terminar aquí (como lo hicimos nosotros) porque querían crear un rasgo reutilizable y pensaban que un componente hijo era una buena manera de hacerlo.

3
  • Hay una lista útil de patrones para crear rasgos reutilizables aquí: reactjs.org/blog/2016/07/13/…joeytwiddle 22/10/18 a las 8:57
  • ¿Qué pasa si tienes N cronómetros y un botón para reiniciarlos todos? ¿Cómo son útiles los accesorios aquí? vsync 26/03/19 a las 19:23
  • @vsync No estoy seguro de que este método pueda ayudarlo en su tarea. Pero la respuesta de Brickingup podría ayudar. Tenga en cuenta que se configuran, this.clickChild = clickpero sus múltiples cronómetros pasarían múltiples funciones, por lo que necesitaría almacenarlos todos:this.watchRestartFuncs[watchId] = restartWatchjoeytwiddle 27/03/19 a las 3:33
3

Estamos contentos con un gancho personalizado que llamamos useCounterKey. Simplemente configura una clave de contador, o una clave que cuenta desde cero. La función que devuelve restablece la tecla (es decir, incremento). (Creo que esta es la forma más idiomática en React para restablecer un componente, simplemente presione la tecla).

Sin embargo, este enlace también funciona en cualquier situación en la que desee enviar un mensaje único al cliente para hacer algo. Por ejemplo, lo usamos para enfocar un control en el niño en un evento padre determinado; simplemente se enfoca automáticamente cada vez que se actualiza la clave. (Si se necesitan más accesorios, se pueden configurar antes de restablecer la clave para que estén disponibles cuando ocurra el evento).

Este método tiene una pequeña curva de aprendizaje porque no es tan sencillo como un controlador de eventos típico, pero parece la forma más idiomática de manejar esto en React que hemos encontrado (ya que las teclas ya funcionan de esta manera). Definitivamente abierto a comentarios sobre este método, ¡pero está funcionando bien!

// Main helper hook:
export function useCounterKey() {
  const [key, setKey] = useState(0);
  return [key, () => setKey(prev => prev + 1)] as const;
}

Usos de muestra:

// Sample 1 - normal React, just reset a control by changing Key on demand
function Sample1() {
  const [inputLineCounterKey, resetInputLine] = useCounterKey();

  return <>
    <InputLine key={inputLineCounterKey} />
    <button onClick={() => resetInputLine()} />
  <>;
}

// Second sample - anytime the counterKey is incremented, child calls focus() on the input
function Sample2() {
  const [amountFocusCounterKey, focusAmountInput] = useCounterKey();

  // ... call focusAmountInput in some hook or event handler as needed

  return <WorkoutAmountInput focusCounterKey={amountFocusCounterKey} />
}

function WorkoutAmountInput(props) {
  useEffect(() => {
    if (counterKey > 0) {
      // Don't focus initially
      focusAmount();
    }
  }, [counterKey]);

  // ...
}

(Crédito a Kent Dodds por el concepto de contraclave ).

3

La lógica es simple.

Create a function in parent using child or use ref.

Prefiero la función de creación en padre con hijo. Hay varias formas de hacerlo.

Cuando se utilizan componentes funcionales

En padre

function Parent(){
  const [functionToCall, createFunctionToCall] = useState(()=>()=>{})

  return (
   <Child createFunctionToCall={createFunctionToCall} />
  )
}

En niño

function Child({createFunctionToCall}){
  useEffect(()=>{
    function theFunctionToCall(){
      // do something like setting something
      // don't forget to set dependancies properly.
    }
    createFunctionToCall(()=>theFunctionToCall)
  },[createFunctionToCall])
}

2

Puedes lograr esto fácilmente de esta manera

Pasos-

  1. Cree una variable booleana en el estado de la clase principal. Actualice esto cuando desee llamar a una función.
  2. Cree una variable prop y asigne la variable booleana.
  3. Desde el componente hijo, acceda a esa variable utilizando accesorios y ejecute el método que desee teniendo una condición if.

    class Child extends Component {
       Method=()=>{
       --Your method body--
       }
       render() {
         return (
        //check whether the variable has been updated or not
          if(this.props.updateMethod){
            this.Method();
          }
         )
       }
    }
    
    class Parent extends Component {
    
    constructor(){
      this.state={
       callMethod:false
      }
    
    }
    render() {
       return (
    
         //update state according to your requirement
         this.setState({
            callMethod:true
         }}
         <Child updateMethod={this.state.callMethod}></Child>
        );
       }
    }
    
2
  • Es posible que desee aislar esto. Parece que va a terminar con un bucle infinito porque el método secundario se ejecutará continuamente porque el estado principal se establece en verdadero. Isaac Pak 3/06/19 a las 13:57
  • @IsaacPak Sí, por eso dejé un comentario allí, diciendo que debe actualizar el estado de acuerdo con sus requisitos. Entonces no se ejecutará como un bucle infinito. Kusal Kithmal 4 de junio de 2019 a las 3:39
2

Aquí mi demostración: https://stackblitz.com/edit/react-dgz1ee?file=styles.css

Estoy usando useEffectpara llamar a los métodos del componente secundario. Lo he intentado, Proxy and Setter_Getterpero sor lejos useEffectparece ser la forma más conveniente de llamar a un método secundario desde el padre. Para usarlo Proxy and Setter_Getter, parece que hay algo de sutileza que superar primero, porque el elemento renderizado en primer lugar es un elemento de objectLike a través de la ref.current return => <div/>especificidad de. En cuanto a useEffect, también puede aprovechar este enfoque para establecer el estado de los padres en función de lo que desee hacer con los hijos.

En el enlace de la demostración que proporcioné, encontrará mi código completo de ReactJS con mi borrador en el interior para que pueda apreciar el flujo de trabajo de mi solución.

Aquí les proporciono el fragmento de mi ReactJS con el código relevante solamente. :

import React, {
  Component,
  createRef,
  forwardRef,
  useState,
  useEffect
} from "react"; 

{...}

// Child component
// I am defining here a forwardRef's element to get the Child's methods from the parent
// through the ref's element.
let Child = forwardRef((props, ref) => {
  // I am fetching the parent's method here
  // that allows me to connect the parent and the child's components
  let { validateChildren } = props;
  // I am initializing the state of the children
  // good if we can even leverage on the functional children's state
  let initialState = {
    one: "hello world",
    two: () => {
      console.log("I am accessing child method from parent :].");
      return "child method achieve";
    }
  };
  // useState initialization
  const [componentState, setComponentState] = useState(initialState);
  // useEffect will allow me to communicate with the parent
  // through a lifecycle data flow
  useEffect(() => {
    ref.current = { componentState };
    validateChildren(ref.current.componentState.two);
  });

{...}

});

{...}

// Parent component
class App extends Component {
  // initialize the ref inside the constructor element
  constructor(props) {
    super(props);
    this.childRef = createRef();
  }

  // I am implementing a parent's method
  // in child useEffect's method
  validateChildren = childrenMethod => {
    // access children method from parent
    childrenMethod();
    // or signaling children is ready
    console.log("children active");
  };

{...}
render(){
       return (
          {
            // I am referencing the children
            // also I am implementing the parent logic connector's function
            // in the child, here => this.validateChildren's function
          }
          <Child ref={this.childRef} validateChildren={this.validateChildren} />
        </div>
       )
}
1

Puede realizar una inversión de herencia (búsquela aquí: https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e ). De esa manera, tiene acceso a la instancia del componente que estaría empaquetando (por lo tanto, podrá acceder a sus funciones)

1

Creo que la forma más básica de llamar a métodos es estableciendo una solicitud en el componente secundario. Luego, tan pronto como el niño maneja la solicitud, llama a un método de devolución de llamada para restablecer la solicitud.

El mecanismo de reinicio es necesario para poder enviar la misma solicitud varias veces una tras otra.

En componente padre

En el método de renderizado del padre:

const { request } = this.state;
return (<Child request={request} onRequestHandled={()->resetRequest()}/>);

El padre necesita 2 métodos para comunicarse con su hijo en 2 direcciones.

sendRequest() {
  const request = { param: "value" };
  this.setState({ request });
}

resetRequest() {
  const request = null;
  this.setState({ request });
}

En componente hijo

El niño actualiza su estado interno, copiando la solicitud de los accesorios.

constructor(props) {
  super(props);
  const { request } = props;
  this.state = { request };
}

static getDerivedStateFromProps(props, state) {
  const { request } = props;
  if (request !== state.request ) return { request };
  return null;
}

Luego, finalmente, maneja la solicitud y envía el restablecimiento al padre:

componentDidMount() {
  const { request } = this.state;
  // todo handle request.

  const { onRequestHandled } = this.props;
  if (onRequestHandled != null) onRequestHandled();
}
1

Otra forma de activar una función secundaria desde el padre es hacer uso de la componentDidUpdatefunción en el componente hijo. Le paso un accesorio triggerChildFuncde Parent to Child, que inicialmente lo es null. El valor cambia a una función cuando se hace clic en el botón y el niño nota ese cambio componentDidUpdatey llama a su propia función interna.

Dado que prop triggerChildFunccambia a una función, también obtenemos una devolución de llamada al Parent. Si Parent no necesita saber cuándo se llama a la función, el valor triggerChildFuncpodría, por ejemplo, cambiar de nulla true.

const { Component } = React;
const { render } = ReactDOM;

class Parent extends Component {
  state = {
    triggerFunc: null
  }

  render() {
    return (
      <div>
        <Child triggerChildFunc={this.state.triggerFunc} />
        <button onClick={() => {
          this.setState({ triggerFunc: () => alert('Callback in parent')})
        }}>Click
        </button>
      </div>
    );
  }
}

class Child extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.triggerChildFunc !== prevProps.triggerChildFunc) {
      this.onParentTrigger();
    }
  }

  onParentTrigger() {
    alert('parent triggered me');

    // Let's call the passed variable from parent if it's a function
    if (this.props.triggerChildFunc && {}.toString.call(this.props.triggerChildFunc) === '[object Function]') {
      this.props.triggerChildFunc();
    }
  }

  render() {
    return (
      <h1>Hello</h1>
    );
  }
}


render(
  <Parent />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>

CodePen: https://codepen.io/calsal/pen/NWPxbJv?editors=1010

1

Estoy usando useEffecthook para superar el dolor de cabeza de hacer todo esto, así que ahora le paso una variable al niño como esta:

<ParentComponent>
 <ChildComponent arbitrary={value} />
</ParentComponent>
useEffect(() => callTheFunctionToBeCalled(value) , [value]);
1

Puede aplicar esa lógica muy fácilmente usando su componente hijo como un gancho personalizado de reacción .

¿Cómo implementarlo?

  • Su hijo devuelve una función.

  • Su hijo devuelve un JSON: {función, HTML u otros valores} como ejemplo.

In the example doesn't make sense to apply this logic but it is easy to see:

const {useState} = React;

//Parent
const Parent = () => {
  //custome hook
  const child = useChild();

  return (
    <div>
      {child.display}          
      <button onClick={child.alert}>
        Parent call child
      </button>
      {child.btn}
    </div>
  );
};

//Child
const useChild = () => {

  const [clickCount, setClick] = React.useState(0);
  
  {/* child button*/} 
  const btn = (
    <button
      onClick={() => {
        setClick(clickCount + 1);
      }}
    >
      Click me
    </button>
  );

  return {
    btn: btn,
    //function called from parent 
    alert: () => {
      alert("You clicked " + clickCount + " times");
    },
    display: <h1>{clickCount}</h1>
  };
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
2
  • ¿Qué pasa con el efecto de uso existente en mi componente hijo? anonymous 31 de enero a las 12:21
  • @anónimo ¿a qué te refieres? Bruno Pop 1 de febrero a las 11:32
0

¿Aquí hay un error? a tener en cuenta: estoy de acuerdo con la solución de rossipedia usando forwardRef, useRef, useImperativeHandle

Hay información errónea en línea que dice que las referencias solo se pueden crear a partir de componentes de React Class, pero de hecho puede usar componentes de función si usa los ganchos antes mencionados. Una nota, los ganchos solo funcionaron para mí después de que cambié el archivo para que no lo use conRouter () al exportar el componente. Es decir, un cambio de

export default withRouter(TableConfig);

en lugar de ser

export default TableConfig;

En retrospectiva, withRouter () no es necesario para un componente de este tipo de todos modos, pero por lo general no daña nada tenerlo. Mi caso de uso es que creé un componente para crear una Tabla para manejar la visualización y edición de los valores de configuración, y quería poder decirle a este componente secundario que restablezca sus valores de estado cada vez que se presione el botón Restablecer del formulario principal. UseRef () no obtendría correctamente ref o ref.current (seguía obteniendo nulo) hasta que eliminé withRouter () del archivo que contiene mi componente secundario TableConfig

0

Para componentes funcionales, la forma más sencilla es

Componente principal

parent.tsx

import React, { useEffect, useState, useRef } from "react";
import child from "../../child"

const parent: React.FunctionComponent = () => {
  const childRef: any = useRef();
}

const onDropDownChange: any = (event): void => {
    const target = event.target;
    childRef.current.onFilterChange(target.value);
  };

 return <child ref={childRef} />

export default parent;

Componente hijo

child.tsx

import React, {   useState,   useEffect,   forwardRef,   useRef,   useImperativeHandle, } from "react";

const Child = forwardRef((props, ref) => {
 useImperativeHandle(ref, () => ({
    onFilterChange(id) {
      console.log("Value from parent", id)
    },
  }));
})

Child.displayName = "Child";

export default Child;