React - Triggering a component method from another component, both belonging in the same render()

38,414

Solution 1

You can define a function on the parent which has a ref to the Foo child. This function will call this.refs.foo.yourFunction(); You then pass that function into the button so when the button is clicked, the parent's function will be called. Added an example here where clicking the button will toggle the state of the Foo component (but it could do anything).

Example below

class Parent extends React.Component {
  triggerFoo() {
    this.foo.toggle();
  }
  render() {
    return (
      <div>
        <Foo ref={foo => this.foo = foo} />
        <Button onClick={this.triggerFoo.bind(this)}/>
      </div>
    );  
  }
}

class Foo extends React.Component {
  state = {foo: false}
  toggle() {
    this.setState({
      foo: !this.state.foo
    });
  }
  render() {
    return (
      <div>
        Foo Triggered: {this.state.foo.toString()}
      </div>
    );
  }
}


class Button extends React.Component {
  render() {
    return (
      <button onClick={this.props.onClick}>
        Click This
      </button>
    );
  };
}
ReactDOM.render(<Parent />, document.getElementById('root'));
<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="root" />

Solution 2

2020 Update

This is how you can do it using functional components and TS

  1. Send setCustomRef to CustomComponent and down to CustomButton to set customRef
  2. Send customRef down to ClickDiv
  3. Invoke customRef.click(); inside ClickDiv

CSB https://codesandbox.io/s/triggering-component-method-from-another-component-skt8o?file=/src/App.tsx

Above Codesandbox does not always work, but I noticed it started working after some time.

import React from "react";
import "./styles.css";
import Button, { ButtonProps } from "@material-ui/core/Button";

export default function App() {
  const [customRef, setCustomRef] = React.useState<HTMLButtonElement | null>(
    null
  );
  return (
    <div className="App">
      <h3>
        React - Triggering a component method from another component, both
        belonging in the same render()
      </h3>
      <p>
        <ClickDiv customRef={customRef} />
      </p>
      <CustomComponent setCustomRef={setCustomRef} />
    </div>
  );
}

type Props = {
  setCustomRef: React.Dispatch<React.SetStateAction<HTMLButtonElement | null>>;
};

const CustomComponent = ({ setCustomRef }: Props) => {
  const anchorRef = React.useRef<HTMLButtonElement>(null);
  const { current } = anchorRef;
  React.useEffect(() => {
    if (current) {
      setCustomRef(current);
    }
  }, [current, setCustomRef]);

  return <CustomButton ref={anchorRef}>Custom Button</CustomButton>;
};

const CustomButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    const onClick = () => {
      alert("CustomButton clicked");
    };
    return (
      <Button ref={ref} {...props} variant="contained" onClick={onClick} />
    );
  }
);

type ClickDivProps = {
  customRef: HTMLButtonElement | null;
};
const ClickDiv = ({ customRef }: ClickDivProps) => {
  const onClick = () => {
    if (customRef) customRef.click();
  };
  return <button onClick={onClick}>Triger Custom Button Click</button>;
};
Share:
38,414
dev
Author by

dev

Updated on October 02, 2020

Comments

  • dev
    dev over 3 years

    I have a custom component Foo, whose 'bar' function I want to trigger from the outside:

    class Foo extends React.Component {
       bar() { ... }
    }
    

    I am rendering Foo along with a button whose purpose is to trigger Foo.bar():

    render() {
    
     return (
         <Foo ...>
         </Foo>
         <Button onClick={I want to trigger Foo.bar()} />
     );
    }
    

    Things I have tried:

    1. Moving <Button > inside Foo, then adding onClick of <Button/> in Foo's constructor -- the binding of onClick didn't work -- I see it attached, but the <Button /> still does not trigger onClick. (or, if it can work, how to do it properly)?

    Solutions that don't do what I want:

    1. I see one potential solution which suggests to listen to a click in the entire MyComponent, then do some DOM querying to find if the actual button was clicked. This won't work for me, however, because MyComponent does not own these buttons (these buttons are not MyComponent.props.children) and I'd prefer to brainstorm a solution that doesn't force me to have to move the buttons inside.

    2. Another potential solution doesn't work: I cannot have the render() return the value of <MyComponent /> into a var myComp, then inside <Button onClick={() => {myComp.blah()}} />, because <Button /> itself is instantiated inside <MyComponent /> as a child! (and, they are both in the same render() call anyways)

    3. Part b) #2 (above), using React-Component static class method, will not work either: I need this on a per-instance basis.

    4. Using app state (such as a store) is not what I want, either -- MyComponent is meant to be a re-usaable widget and cannot be tied to a specific application state or listening to any specific stores or store actions.

    Any suggestions?

    Thanks

  • dev
    dev about 7 years
    Yes, this would work wonderfully. Thanks very much for the clear explanation and suggestion :)
  • dev
    dev about 7 years
    Quick note: It appears 'string' refs are not supported by the latest react version? According to the latest react docs (Feb 2017), ref now takes a callback which has the element as the first param. So your above would be ref={(foo) => {this.foo = foo}} (I was actually getting a hard invariant violation using the legacy ref api, but using callback pattern fixed the issue)
  • noveyak
    noveyak about 7 years
    Yea, that's a good idea. String refs should still work for the general case but depending on how your code is structured and 'this' is bound it seems like it can lead to problems. I updated the answer to reflect using a callback for ref.
  • hello world
    hello world almost 6 years
    i searched everything on refs & this was the best example. ty
  • Shubham Baranwal
    Shubham Baranwal over 5 years
    this answer really helpful :) @noveyak
  • Ryan_DS
    Ryan_DS about 4 years
    Now that functional components are the way to go and refs are discouraged from use, how would you do this using only functional components? Move the button into the function component?