127

I want to call a method exposed by a React component from the instance of a React Element.

For example, in this jsfiddle. I want to call the alertMessage method from the HelloElement reference.

Is there a way to achieve this without having to write additional wrappers?

Edit (copied code from 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')
);
Edgar
  • 6,022
  • 8
  • 33
  • 66
  • 3
    Not ideal, but JSFiddle is common enough that it doesn't warrant a down-vote. – Jeff Fairley Jul 24 '15 at 16:25
  • I'm wondering what could be your use case that would warrant such a thing. This isn't a good way to design your application imo. If you do need to resuse something, please create a separate common helper in a third file and use it for your button as well as your react component. – teaflavored Jan 21 '17 at 23:01

14 Answers14

63

You can do like

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;

Now from outside of this component you can called like this below

window.helloComponent.alertMessage();
Kushal Jain
  • 3,029
  • 5
  • 31
  • 48
  • 2
    In fact, simple and functional! Like it is supposed to be. I am very impressed with how simple it is; really didn't think about. Probably this approach won't work that good if there are more and more components you should. Thank you! – Leonardo Maffei Sep 28 '18 at 17:19
  • 11
    Adding a global variable is not a good solution. More on why global variables are bad here: http://wiki.c2.com/?GlobalVariablesAreBad – Salvatore Zappalà Nov 30 '18 at 11:23
  • 7
    Thanks for down vote!! Yes global variable are not good but that's a way to solve your problem. – Kushal Jain Nov 30 '18 at 11:38
  • 3
    This is exactly the pragmatic and simple solution I needed, thanks ! – Florent Destremau Mar 12 '19 at 15:09
  • Even though this approach uses global window variable to save the reference to this, it worked for me. Thanks for this neat solution ! – Binita Bharati Mar 21 '19 at 17:48
  • 3
    it worked but it won't work if you have mutiple same component on the same page. – gaurav Jul 18 '19 at 13:17
  • 4
    How would you do this with functional react where 'this' doesn't exist? – user6329530 Jul 16 '20 at 13:19
  • 1
    Thank you so much...I needed it so badly – Partha Paul Nov 09 '20 at 14:53
  • Works when global variable is useable. – Nicholas DiPiazza Jun 17 '21 at 15:33
  • Agree that global variables are bad. But, when react makes such a simple concept as calling another component's function as complicated as possible with createRef vs useRef, function components vs class components, useImperativeHandle, confusing compile errors regarding IntrinsicAttributes... they're making global variables a *very* attractive option. React has chosen to force developers into anti-patterns rather than making this a first class and intuitive feature like angular has done. – l p Apr 25 '22 at 02:34
62

There are two ways to access an inner function. One, instance-level, like you want, another, static level.

Instance

You need to call the function on the return from React.render. See below.

Static

Take a look at ReactJS Statics. Note, however, that a static function cannot access instance-level data, so this would be 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'));

Then do HelloRendered.alertMessage().

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Jeff Fairley
  • 8,071
  • 7
  • 46
  • 55
  • 15
    Note that using the return value of render is considered deprecated, and expected to be removed in future versions to enable performance enhancements. The supported way of getting a reference to your component instance object is to add a property `ref` which is a function called with the instance as a parameter. This also allows you to access objects that are not at the top level, e.g. if you're rendering `` you get the right reference passed to your `setHelloRef` function rather than one to `MuiThemeProvider`. – Periata Breatta Sep 30 '16 at 00:25
  • Uncaught TypeError: _react2.default.render is not a function – Partha Paul Nov 09 '20 at 14:16
43

1. With React hooks - useImperativeHandle + useRef

const MyComponent = ({myRef}) => {
  const handleClick = () => alert('hello world')
  useImperativeHandle(myRef, () => ({
    handleClick
  }), [/* dependencies (if any) */])
  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>
    </>
  )
}

2. With only 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>
    </>
  )
}

Good Luck...

Aakash
  • 21,375
  • 7
  • 100
  • 81
28

I've done something like this:

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

}

And then somewhere else:

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

}

I haven't tested this exact code, but this is along the lines of what I did in a project of mine and it works nicely :). Of course this is a bad example, you should just use props for this, but in my case the sub-component did an API call which I wanted to keep inside that component. In such a case this is a nice solution.

gitaarik
  • 42,736
  • 12
  • 98
  • 105
6

With the render method potentially deprecating the returned value, the recommended approach is now to attach a callback ref to the root element. Like this:

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

which we can then access using window.helloComponent, and any of its methods can be accessed with window.helloComponent.METHOD.

Here's a full example:

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
  • 59,771
  • 29
  • 135
  • 184
  • Better to put the reference in local state instead of on the window object... `ref={component => {this.setState({ helloComponent: component; })}}` Then, in click handler... `this.state.helloComponent.alertMessage();` – Bill Dagg Oct 18 '19 at 16:29
  • Further to my previous comment... or, better yet, just put the component's alertMessage method in state. – Bill Dagg Oct 18 '19 at 19:10
  • When I try this I get `Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?` and it doesn't bind component to window – Guerrilla Sep 02 '20 at 02:58
  • Maybe not great form, but did exactly what I needed it to. Thank you – Sean Jan 26 '21 at 21:13
3

You can just add an onClick handler to the div with the function (onClick is React's own implementation of onClick) and you can access the property within { } curly braces, and your alert message will appear.

In case you wish to define static methods that can be called on the component class - you should use statics. Although:

"Methods defined within this block are static, meaning that you can run them before any component instances are created, and the methods do not have access to the props or state of your components. If you want to check the value of props in a static method, have the caller pass in the props as an argument to the static method." (source)

Some example code:

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

Hope this helps you a bit, because i don't know if I understood your question correctly, so correct me if i interpreted it wrong:)

holms
  • 9,112
  • 14
  • 65
  • 95
3

It appears statics are deprecated, and the other methods of exposing some functions with render seem convoluted. Meanwhile, this Stack Overflow answer about debugging React, while seeming hack-y, did the job for me.

Community
  • 1
  • 1
philipkd
  • 910
  • 7
  • 20
3

If you are in ES6 just use the "static" keyword on your method from your example would be the following: static alertMessage: function() { ...
},

Hope can help anyone out there :)

darmis
  • 2,879
  • 1
  • 19
  • 21
  • You can not reach props or state in a static function. – Serdar D. Oct 05 '17 at 02:16
  • Ok yes, but the question was about to access the alertMessage() function so you could use HelloElement.alertMessage(). – darmis Oct 06 '17 at 09:20
  • Generally calling a function without using props and state has no effect. But as you are right about reaching function, I am removing my vote – Serdar D. Oct 06 '17 at 15:35
3
class AppProvider extends Component {
  constructor() {
    super();

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

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

You can call this method from the window by using window.alertMessage().

  • 2
    This would work but adding a global variable is not a good solution, for many reasons. You can find more info on why global variables are bad here: http://wiki.c2.com/?GlobalVariablesAreBad – Salvatore Zappalà Nov 30 '18 at 11:18
3

I use this helper method to render components and return an component instance. Methods can be called on that instance.

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
  • 10,010
  • 7
  • 61
  • 117
2

method 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>

Method 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
  • 18,813
  • 10
  • 112
  • 118
1

With React17 you can use useImperativeHandle hook.

useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef:

function FancyInput(props, ref) {
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({
        focus: () => {
            inputRef.current.focus();
        }
    }));

    return <input ref={inputRef} ... />;
}

FancyInput = forwardRef(FancyInput);

In this example, a parent component that renders would be able to call inputRef.current.focus().

1

Though this question is closed, I would like to share another approach.

Here's what worked for me:

Child Component

  1. Child component accepts a prop, let's call it onExportedMethods, the aim is to return the set of instance methods that this component wants to give to consumers.

  2. The decision of what needs to be exposed is done at constructor level.

Consumer Component

  1. pass method for prop onExportedMethods & in the handler keep copy of the set of methods Child component exposes.

  2. Whenever required, parent component can call the exposed method

Checkout the sample here

ram adhikari
  • 323
  • 1
  • 5
0

For dynamic components I used the getDerivedStateFromProps method with props.

You can create function that update the props of the child component, The getDerivedStateFromProps in the child component will handle the update of the props for you. For example:

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

        this.state = {  selectMachine: '1' };

        this.setComponent = null;
       
    }

     handleMachineChange = (e) =>{
        this.setState({selectMachine: e.target.value})
    }

}

class Child extends React.Component
{
    state = {
        programForm: {
            machine_id: '1',
           }
    }

    constructor(props)
    {
        super(props);
    }


    static getDerivedStateFromProps(props, state) {
        if(props.selectMachine !== state.programForm.machine_id){
            //Change in props
            return{
                programForm:  { ...state.programForm, machine_id: props.selectMachine }
            };
        }
        return null; // No change to state
    }
}
Oras
  • 120
  • 1
  • 9