Llamar a un método de componente React desde fuera

95

Quiero llamar a un método expuesto por un componente React desde la instancia de un Elemento React.

Por ejemplo, en este jsfiddle . Quiero llamar al alertMessagemétodo desde la HelloElementreferencia.

¿Hay alguna manera de lograr esto sin tener que escribir envoltorios adicionales?

Editar (código copiado de JSFiddle)

<div id="container"></div>
<button onclick="onButtonClick()">Click me!</button>
var onButtonClick = function () {

    //call alertMessage method from the reference of a React Element! Something like HelloElement.alertMessage()
    console.log("clicked!");
}

var Hello = React.createClass({displayName: 'Hello',

    alertMessage: function() {
        alert(this.props.name);                             
    },

    render: function() {
        return React.createElement("div", null, "Hello ", this.props.name);
    }
});

var HelloElement = React.createElement(Hello, {name: "World"});

React.render(
    HelloElement,
    document.getElementById('container')
);
Rico guerrero
fuente
3
No es ideal, pero JSFiddle es lo suficientemente común como para no justificar un voto negativo.
Jeff Fairley
Me pregunto cuál podría ser su caso de uso que justificaría tal cosa. Esta no es una buena manera de diseñar su aplicación en mi opinión. Si necesita volver a usar algo, cree un ayudante común separado en un tercer archivo y utilícelo para su botón, así como para su componente de reacción.
sabor a té

Respuestas:

56

Hay dos formas de acceder a una función interna. Uno, nivel de instancia, como quieras, otro nivel estático.

Ejemplo

Necesita llamar a la función al regresar de React.render. Vea abajo.

Estático

Eche un vistazo a ReactJS Statics . Sin embargo, thistenga en cuenta que una función estática no puede acceder a los datos a nivel de instancia, por lo que también lo sería undefined.

var onButtonClick = function () {
    //call alertMessage method from the reference of a React Element! 
    HelloRendered.alertMessage();
    //call static alertMessage method from the reference of a React Class! 
    Hello.alertMessage();
    console.log("clicked!");
}

var Hello = React.createClass({
    displayName: 'Hello',
    statics: {
        alertMessage: function () {
            alert('static message');
        }
    },
    alertMessage: function () {
        alert(this.props.name);
    },

    render: function () {
        return React.createElement("div", null, "Hello ", this.props.name);
    }
});

var HelloElement = React.createElement(Hello, {
    name: "World"
});

var HelloRendered = React.render(HelloElement, document.getElementById('container'));

Entonces hazlo HelloRendered.alertMessage().

Jeff Fairley
fuente
13
Tenga en cuenta que el uso del valor de retorno de renderizado se considera obsoleto y se espera que se elimine en versiones futuras para permitir mejoras de rendimiento. La forma admitida de obtener una referencia a su objeto de instancia de componente es agregar una propiedad refque es una función llamada con la instancia como parámetro. Esto también le permite acceder a objetos que no están en el nivel superior, por ejemplo, si está renderizando <MuiThemeProvider><Hello ref={setHelloRef} /></MuiThemeProvider>, obtiene la referencia correcta que se pasa a su setHelloReffunción en lugar de una a MuiThemeProvider.
Periata Breatta
Uncaught TypeError: _react2.default.render no es una función
Partha Paul hace
46

Puedes hacer como

import React from 'react';

class Header extends React.Component{

    constructor(){
        super();
        window.helloComponent = this;
    }

    alertMessage(){
       console.log("Called from outside");
    }

    render(){

      return (
      <AppBar style={{background:'#000'}}>
        Hello
      </AppBar>
      )
    }
}

export default Header;

Ahora, desde fuera de este componente, puede llamar así a continuación

window.helloComponent.alertMessage();
Kushal Jain
fuente
2
De hecho, ¡simple y funcional! Como se supone que debe ser. Estoy muy impresionado con lo simple que es; realmente no pensé. Probablemente este enfoque no funcionará tan bien si hay más y más componentes que debería. ¡Gracias!
Leonardo Maffei
6
Agregar una variable global no es una buena solución. Más sobre por qué las variables globales son malas aquí: wiki.c2.com/?GlobalVariablesAreBad
Salvatore Zappalà
4
¡Gracias por votar en contra! Sí, las variables globales no son buenas, pero esa es una forma de resolver su problema.
Kushal Jain
1
Esta es exactamente la solución pragmática y simple que necesitaba, ¡gracias!
Florent Destremau
2
funcionó, pero no funcionará si tiene varios componentes iguales en la misma página.
gaurav
26

He hecho algo como esto:

class Cow extends React.Component {

    constructor (props) {
        super(props);
        this.state = {text: 'hello'};
    }

    componentDidMount () {
        if (this.props.onMounted) {
            this.props.onMounted({
                say: text => this.say(text)
            });
        }
    }

    render () {
        return (
            <pre>
                 ___________________
                < {this.state.text} >
                 -------------------
                        \   ^__^
                         \  (oo)\_______
                            (__)\       )\/\
                                ||----w |
                                ||     ||
            </pre>
        );
    }

    say (text) {
        this.setState({text: text});
    }

}

Y luego en otro lugar:

class Pasture extends React.Component {

    render () {
        return (
            <div>
                <Cow onMounted={callbacks => this.cowMounted(callbacks)} />
                <button onClick={() => this.changeCow()} />
            </div>
        );
    }

    cowMounted (callbacks) {
        this.cowCallbacks = callbacks;
    }

    changeCow () {
        this.cowCallbacks.say('moo');
    }

}

No he probado este código exacto, pero está en la línea de lo que hice en un proyecto mío y funciona muy bien :). Por supuesto, este es un mal ejemplo, solo debería usar accesorios para esto, pero en mi caso, el subcomponente hizo una llamada a la API que quería mantener dentro de ese componente. En tal caso, esta es una buena solución.

gitaarik
fuente
5
Creo que quisiste decirthis.cowCallbacks.say('moo')
Steven
Por cierto, es posible pasar thisa callback (que será una instancia de Cow) en lugar de callbacks.
WebBrother
@WebBrother Sí, pero eso sería aún más hacky
gitaarik
6

Con el rendermétodo potencialmente desaprobando el valor devuelto, el enfoque recomendado ahora es adjuntar una referencia de devolución de llamada al elemento raíz. Me gusta esto:

ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));

al que luego podemos acceder usando window.helloComponent, y cualquiera de sus métodos se puede acceder con window.helloComponent.METHOD.

Aquí tienes un ejemplo completo:

var onButtonClick = function() {
  window.helloComponent.alertMessage();
}

class Hello extends React.Component {
  alertMessage() {
    alert(this.props.name);
  }

  render() {
    return React.createElement("div", null, "Hello ", this.props.name);
  }
};

ReactDOM.render( <Hello name="World" ref={(element) => {window.helloComponent = element}}/>, document.getElementById('container'));
<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="container"></div>
<button onclick="onButtonClick()">Click me!</button>

Brett DeWoody
fuente
Es mejor poner la referencia en el estado local en lugar de en el objeto de la ventana ... ref={component => {this.setState({ helloComponent: component; })}} Luego, en el controlador de clic ... this.state.helloComponent.alertMessage();
Bill Dagg
Además de mi comentario anterior ... o, mejor aún, simplemente ponga el método alertMessage del componente en estado.
Bill Dagg
Cuando intento esto, obtengo Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?y no vincula el componente a la ventana
Guerrilla
4
class AppProvider extends Component {
  constructor() {
    super();

    window.alertMessage = this.alertMessage.bind(this);
  }

  alertMessage() {
    console.log('Hello World');
 }
}

Puede llamar a este método desde la ventana usando window.alertMessage().

Mygel Bergstresser
fuente
Esto funcionaría, pero agregar una variable global no es una buena solución, por muchas razones. Puede encontrar más información sobre por qué las variables globales son malas aquí: wiki.c2.com/?GlobalVariablesAreBad
Salvatore Zappalà
3

Si está en ES6 simplemente use la palabra clave "estática" en su método de su ejemplo, sería la siguiente: static alertMessage: function() { ...
},

Hope puede ayudar a cualquiera :)

darmis
fuente
No se puede alcanzar apoyos o estados en una función estática.
Serdar Değirmenci
Ok, sí, pero la pregunta estaba a punto de acceder a la función alertMessage () para que pudiera usar HelloElement.alertMessage ().
darmis
Generalmente, llamar a una función sin usar props y state no tiene ningún efecto. Pero como tienes razón sobre alcanzar la función, estoy eliminando mi voto
Serdar Değirmenci
3

Con React hook - useRef



const MyComponent = ({myRef}) => {
  const handleClick = () => alert('hello world')
  myRef.current.handleClick = handleClick
  return (<button onClick={handleClick}>Original Button</button>)
}

MyComponent.defaultProps = {
  myRef: {current: {}}
}

const MyParentComponent = () => {
  const myRef = React.useRef({})
  return (
    <>
      <MyComponent 
        myRef={myRef}
      />
      <button onClick={myRef.current.handleClick}>
        Additional Button
      </button>
    </>
  )
}

Buena suerte...

Aakash
fuente
2

Puede simplemente agregar un onClickcontrolador al div con la función ( onClickes la propia implementación de React onClick) y puede acceder a la propiedad dentro{ } llaves, y aparecerá su mensaje de alerta.

En caso de que desee definir métodos estáticos que se pueden llamar en la clase de componente, debe utilizar estáticos. A pesar de que:

"Los métodos definidos dentro de este bloque son estáticos, lo que significa que puede ejecutarlos antes de que se creen instancias de componentes, y los métodos no tienen acceso a los accesorios o al estado de sus componentes. Si desea verificar el valor de los accesorios en un método, haga que la persona que llama pase los accesorios como un argumento para el método estático ". ( fuente )

Algún código de ejemplo:

    const Hello = React.createClass({

        /*
            The statics object allows you to define static methods that can be called on the component class. For example:
        */
        statics: {
            customMethod: function(foo) {
              return foo === 'bar';
            }
        },


        alertMessage: function() {
            alert(this.props.name);                             
        },

        render: function () {
            return (
                <div onClick={this.alertMessage}>
                Hello {this.props.name}
                </div>
            );
        }
    });

    React.render(<Hello name={'aworld'} />, document.body);

Espero que esto te ayude un poco, porque no sé si entendí tu pregunta correctamente, así que corrígeme si la interpreté mal :)

Danillo Felixdaal
fuente
2

método 1 using ChildRef :

public childRef: any = React.createRef<Hello>();

public onButtonClick= () => {
    console.log(this.childRef.current); // this will have your child reference
}

<Hello ref = { this.childRef }/>
<button onclick="onButtonClick()">Click me!</button>

Método 2: using window register

public onButtonClick= () => {
    console.log(window.yourRef); // this will have your child reference
}

<Hello ref = { (ref) => {window.yourRef = ref} }/>`
<button onclick="onButtonClick()">Click me!</button>
Mohideen bin Mohammed
fuente
El método 1 es una forma muy fácil y limpia de acceder a los métodos de componentes secundarios. ¡Gracias!
gabdara
1

Utilizo este método auxiliar para renderizar componentes y devolver una instancia de componente. Los métodos se pueden llamar en esa instancia.

static async renderComponentAt(componentClass, props, parentElementId){
         let componentId = props.id;
        if(!componentId){
            throw Error('Component has no id property. Please include id:"...xyz..." to component properties.');
        }

        let parentElement = document.getElementById(parentElementId);

        return await new Promise((resolve, reject) => {
            props.ref = (component)=>{
                resolve(component);
            };
            let element = React.createElement(componentClass, props, null);
            ReactDOM.render(element, parentElement);
        });
    }
Stefan
fuente