kriasoft / react-starter-kit

The web's most popular Jamstack front-end template (boilerplate) for building web applications with React
https://reactstarter.com
MIT License
22.68k stars 4.16k forks source link

How to call child component method from parent? #909

Closed devk1d closed 7 years ago

devk1d commented 7 years ago

Child.js

import s from './Child.css';

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

export default withStyles(s)(Child);

Parent.js

class Parent extends Component {
 render() {
  onClick() {
    this.refs.child.getAlert() // undefined
  }
  return (
    <div>
      <Child ref="child" />
      <button onClick={this.onClick.bind(this)}>Click</button>
    </div>
  );
 }
}
frenzzy commented 7 years ago

For example you can use Refs to Components approach like so:

/* Child.js */
import React from 'react'
import withStyles from 'isomorphic-style-loader/lib/withStyles'
import s from './Child.css'

class Child extends React.Component {
  componentDidMount() {
    this.props.onRef(this)
  }
  componentWillUnmount() {
    this.props.onRef(undefined)
  }
  method() {
    window.alert('do stuff')
  }
  render() {
    return <h1 className={s.root}>Hello World!</h1>
  }
}

export default withStyles(s)(Child);
/* Parent.js */
import React from 'react'
import Child from './Child'

class Parent extends React.Component {
  onClick = () => {
    this.child.method() // do stuff
  }
  render() {
    return (
      <div>
        <Child onRef={ref => (this.child = ref)} />
        <button onClick={this.onClick}>Child.method()</button>
      </div>
    );
  }
}

Demo: https://jsfiddle.net/frenzzy/z9c46qtv/

devk1d commented 7 years ago

@frenzzy it works! tks!

martinnov92 commented 7 years ago

Hello @frenzzy , first I want to thank you for amazing solution. It worked like charm, but could you please explain me what this:

  componentDidMount() {
    this.props.onRef(this)
  }

is exactly doing? I am new in this so I would like to know why it works? :D

Thank you very much

Sartonon commented 7 years ago

When you call the onRef function inside Child-component, you are calling the function passed from the parent component. this.props.onRef(this) Here parameter this points to the child component itself and parent component receives that reference as the first parameter: onRef={ref => (this.child = ref)} and then it saves the reference with key "child". After that you have to whole child component reference accessible inside the parent component and you can call the child components functions.

for example:

this.child.method()

martinnov92 commented 7 years ago

@Sartonon Thank you very much for nice explanation!

Vamshikrishna209 commented 7 years ago

@Sartonon I am using a child component which is repeated many times dynamically , How can i give refs for that dynamic Components and Call them respectively on click ? Can you please help me with this.

frenzzy commented 7 years ago

@Vamshikrishna209 you can save references like so:

<div>
  {this.props.array.map((item, index) =>
    <ChildComponent onRef={ref => (this[`example${index}`] = ref)} />
  )]
</div>

and then get access to it

this.example0.childComponentMethod();
// or
this[`example${index}`].childComponentMethod();
langpavel commented 7 years ago

This is antipattern!

ThaJay commented 7 years ago

I just randomly found this because I want to write unit tests for component functions but to anyone coming here in the future: @langpavel is right, do not do this! You would not buy six hammers for six nails, you would grab the same hammer each time.

So define the method on the parent, pass it to the child as prop and call it in the child;

class Thing extends Component {
  render () {
    return (
      <View>
        {getThings()}
      </View>
    )
  }

  getThings () {
    const thingNodes = []
    for (let thing of this.props.things) {
      thingNodes.push(<Child key={thing.id} name={thing.name} doStuff={this.doStuff} />)
    }
    return thingNodes
  }

  doStuff () {
    console.log(this.props.name) // thing.name as passed in getThings as this function is not bound to its defining class.
  }
}

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

this in doStuff will refer to the calling component Child unless you doStuff={doStuff.bind(this)} when you call Child in the render function of Thing.

(but binding to this on the component call will create a new function each time the parent renders so if the performance matters you should use one of the other ways of binding. I used this way because its usually good enough and very readable.)

dmr07 commented 7 years ago

@langpavel How should the app be structured so as to avoid this design pattern, if the parent must call a child function so as to avoid code bloat.

langpavel commented 7 years ago

@dmr07 If you need call child function, it indicates lack of data model in your app. If you use redux, you just call an action creator. This can be complex, you can use redux-thunk or sagas or what you want, and as action creator creates actions, state will update and connected components will rerender. You can of course use different approaches, like RxJS, but I highly recommend redux – it's simple and it will direct you to the right reactive way.. :wink:

sahanDissanayake commented 7 years ago

@langpavel why is this an anitpattern ?

eromoe commented 7 years ago

But how to set ref to this.props.children ???

<Parent>
   <Child/ >
</Parent>

I want to call Child method in Parent , Parent is a common wrapper which require a Child with specific function . Child is dynamic,

import React from 'react'
// can not import Child here
// import Child from './Child'  

class Parent extends React.Component {
  onClick = () => {
    this.props.children.method() // do stuff
  }
  render() {

    const {children} = this.props;
    return (
      <div>
        {children}
        <button onClick={this.onClick}>Child.method()</button>
      </div>
    );
  }
}

Any idea?

ThaJay commented 7 years ago

It does not work like that. Restructure your data model as said before like so:


import React, {Component} from 'react'
import Child from './child.js'

class Parent extends Component {
  constructor (props) {
    super(props)
    this.state = {
      item: ''
    }
  }

  render () {
    <div>
      <Child setItem={item => this.setState({item})} item={this.state.item} />
    </div>
  }
}

if this becomes too complex use Redux or Mobx.

If there is part of the functionality in onClick thet needs to be defined on the child, just give it a callback:

constructor (props) {
  super(props)
  this.onClick = this.onClick.bind(this)
}

onClick (callback, state) {
  // do things
  callback()
  this.setState(state)
}

(But first think about the data model and code structure. It is probably not needed. I only call state changing functions on children and stuff just happens because of a re-render.)

eromoe commented 7 years ago

@ThaJay No, that is not what I want, Child need to be dynamic . Parent only need the function inside Child , but shouldn't hardcode Child inside it.

For example:

  1. Child keep all Child's code.(For example, a form with submit logic)
  2. Parent is a common wrapper. (For example, a dialog used to wrap a form)
  3. Index.js don't care them

I have worked it around by using React.cloneElement to pass ref to children.

  1. Child

    class Child extends React.Component {
    
      handleSubmit() {
        //...
      }
    
      render() {
    
        return (
          <div>
            ....
          </div>
        );
      }
    }
  2. Parent

    import React from 'react'
    
    class Parent extends React.Component {
    
      onClick() {
        // fist child need have handleSubmit
        this.refs.child0.handleSubmit()
        this.toggleDialog()
      }
    
      render() {
    
        const children = React.Children.map(this.props.children,
          (child, index) => React.cloneElement(child, {
            ref : `child${index}`
          })
         );
    
        return (
            <div>
              <DialogContent>
                {children}
              </DialogContent>
            <button onClick={this.onClick}></button>
          </div>
        );
      }
    }
  3. index.js

    <Parent>
       <Child/ >
    </Parent>

Detail see my question: https://stackoverflow.com/a/46187469/1637673

ThaJay commented 7 years ago

Perhaps something like this?

index.js

import React from 'react'

class Parent extends React.Component {
  handleSubmit1 () {}

  handleSubmit2 () {}

  render () {
    return (
      <div>
        <Dialog visible={this.state.dialog1Visible} onClick={this.handleSubmit1}>
          <DialogChild1 />
        </Dialog>

        <Dialog visible={this.state.dialog2Visible} onClick={this.handleSubmit2}>
          <DialogChild2 />
        </Dialog>
      </div>
      // button is in Dialog component
    )
  }
}

Get creative with your architecture, there surely is a way to structure the code better. Or you haven't explained in enough detail why it it needed. But as react-starter-kit is a beginner package I assume the first.

ThaJay commented 7 years ago

I did not read your point properly. You want to pass child from higher up. See the following example. Of course you can use this.props.children but the child has to be selected somewhere.

import React, {Component} from 'react'
import constants  from './constants.js'
import children   from './children.js'
import submitters from './submitters.js'

class Parent extends Component {
  constructor(props) {
    super(props)  
    this.state = {
      child   :'',
      onSubmit:''
    }
  }

  componentWillMount () {
    const name     = constants[this.props.childName]
    const child    = children[name]
    const onSubmit = submitters[name]

    this.setState({child, onSubmit})
  }

  render () {
    return (
      <div>
        <Dialog onSubmit={this.state.onSubmit}>
          {this.state.child}
        </Dialog>
      </div>
    )
  }
}

A child does not have submit logic. A child only has view. Parent also sounds like a view. Parent should have a parent that decides how its children render. I can call submit on a prop or it can call a setter passed as a prop. Submit logic should live somewhere else, like on the data model for instance (orm style), or in a http request module.

andrewnaeve commented 6 years ago

I have a parent that needs to call its child component every time a scroll event triggers for custom lazy loading. I have it set up so that when the parent is scrolled, setState toggles a boolean. The child receives this with componentWillReceiveProps, and triggers the child to check its position in the viewport. Problem is that of course, each received prop triggers a re-render. About 1000 of them. I've debounced it, but am looking for a more elegant solution.

I came here looking to circumvent updating state, hoping that the parent could simply tell the child to check its position on scroll, thereby avoiding re-render if not necessary. Is using refs still a bad idea in this case?

ThaJay commented 6 years ago

I would not bother thinking about the logic and just use a module. A quick google gave me the following: https://www.npmjs.com/package/react-list-lazy-load You could also just use it for inspiration. A quick glance at the code tells me the children are dumb like they should be.

jose920405 commented 6 years ago

@frenzzy

Thanks, your method is very similar to the delegates in the native ios

henrikra commented 6 years ago

@frenzzy How can you make HOC out of that logic?

So always when I want to get ref I don't have to add those componentDidMount etc. I would like to wrap a component to a HOC like this withRef(MyChild)

frenzzy commented 6 years ago

@henrikra you don't need HOC for this, example:

import React from 'react'

class Child extends React.Component {
  render() {
    return <label>Input: <input ref={this.props.inputRef} /></label>
  }
}

class Parent extends React.Component {
  componentDidMount() {
    this.input.focus() // <= access to child ref
  }

  render() {
    return <Child inputRef={ref => this.input = ref} />
  }
}
grneggandsam commented 6 years ago

Ok, I don't understand all the people who think this is a structural problem and not needed.

Situation: You have a FEW DIFFERENT parent components which both contain the same child component. The child component is Alerts (which contains all the Alerts at the top of the page).

The same methods will need to be called within the child components. The child component will always need to have an addAlert and removeAlert function. These methods are called through the form submits of the parent component.

Wouldn't it be nice if you would not have to re-write the methods passed down to the child components? Doesn't seem very DRY if you can't.

koistya commented 6 years ago

Using an imperative handler approach:

./Child.tsx

import { forwardRef, useImperativeHandle, useState } from "react";

export const Child = forwardRef(function Child(props, ref): JSX.Element {
  const [open, setOpen] = useState(false);

  useImperativeHandle(ref, () => ({
    open() {
      setOpen(true);
    },
    close() {
      setOpen(false);
    }
  }), []);

  return (
    <div {...props}>...</div>
  );
});

./Parent.tsx

import { useRef } from "react";
import { ChildRef } from "./Child";

export function Parent(): JSX.Element {
  const childRef = useRef<ChildRef>(null);

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current?.open()}>Open</button>
    </div>
  );
}

Alternatively, using an event emitter:

https://medium.com/@tarkus/common-javascript-tasks-without-redux-f23ffbbb02ae

Or, using a state manager such as Recoil.

vitto-moz commented 6 years ago

Today I find next solution I need to get my component wrapped with a function (because it receives url from routing functionality) Child component looks like this

const externalVideo = ({uri}) => {
  return <VideoBlock uri={uri}>
              {(parent) => <ExternalVideo parent={parent} ref={ref => parent.webViewComponent = ref }/>}
        </VideoBlock>
}

VideoBlock component has next render method

render() {
    return (
      <View style={styles.videoBlockWrap}>
          {this.props.children(this)}
      </View>
    );
  }

After this preparations, I can simply invoke child's method with next syntax: method in VideoBlock component (parent)

onCreateTopicPress = () => {
    this.webViewComponent.getCurrentSelection()
  }
goredefex commented 6 years ago

I don't fully agree with things in this conversation and I have no idea how to get it to change in the React world of design patters. Calling methods on a child component should not be an anti-pattern or considered a bad pattern. It should be encouraged given certain use cases.

For example: I have a silly game app and all you have to do is drag little circles from one side of the screen to the other. Sometimes there can be over 200 of them at once on screen. Many MANY times I need to know the positions and a number of state values in my child components of these circles. Also, many other times, I need the parent (which is the game itself) to not only house all of the logic of the game, which the circles should NOT know, but also update things based on it's rules. Often this involves changing the state of one of the components...

To say that I should be putting over 200 items and their states into Redux or application state is total poppycock! Application state is supposed to be simple, clean, derivable objects and not to go crazy with because of badly things can get slowed down if you keep choosing to just casually throw everything into app state. Now imagine as I drag them, that I need to update their state through redux every pixel! Not only am I updating a massive state object consistently but I am also causing render logic to have to ripple down through the components using this state...

Why not have the ability for the parent to call a clean, concise method to access it's own component state safely? It would be clean because the component owns it's own state changing functions and can allow/disallow clean/dirty choices of access.

pstanton commented 6 years ago

the desire to use an antipatern usually identifies a shortcoming in the framework

jamessawyer commented 6 years ago

how can I pass a 'ref' to Child Component from Parent Component?

// Child Component
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { TextInput } from 'react-native';

export default class MyTextInput extends PureComponent {
  // child method
  // to get focus the input
  getFocus = () => {
    this.refs[this.props.next].focus()
  }
  render() {
    return (
      <TextInput
        ref = {this.props.next}
        autoCapitalize='none'
        autoCorrect={false}
        underlineColorAndroid='transparent'
        allowFontScaling={false}
        {...this.props}
      />
    );
  }
}

// parent Component
class Parent extends PureComponent {
  return (
    <View>
      <MyTextInput
        onSubmitEditing={() => {}} // I wanna use child component's getFocus() method here
      />
      <MyTextInput
        next='childRef'
      />
    </View>
  )
}
joeytwiddle commented 6 years ago

I found a reasonably sensible solution to this problem by 'brickingup' on StackOverflow.

The idea is that the parent passes a callback function to the child, and the child calls that function, to pass its method(s) to the parent. The parent can then store the methods locally.

The important code is commented below.

class Parent extends Component {
  acceptMethods(childDoAlert) {
    // Parent stores the method that the child passed
    this.childDoAlert = childDoAlert;
  }
  render() {
    return (
      <div>
        {/* Give the child a callback, so it can share its methods with us. */}
        <Child shareMethods={this.acceptMethods.bind(this)}/>
        {/* Later we can call the child's method directly! */}
        <button onClick={() => this.childDoAlert()}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  componentDidMount() {
    // Child passes its method to the parent
    this.props.shareMethods(this.doAlert.bind(this));
  }
  doAlert() {
    alert('clicked');
  }
  render() {
    return (
      <h1>Hello</h1>
    );
  }
}

If you want to receive more than one method from the child, then change shareMethods and acceptMethods to pass an object instead of just one function.

For clarity, I think the acceptMethods function should be placed as close to the constructor as possible.

francisngo commented 6 years ago

With a little tweak of @frenzzy's solution, I was able to get it to work with "withStyles" higher order component from isomorphic-style-loader

crrobinson14 commented 6 years ago

I realize this issue is closed. The ONLY reason I'm replying to it is because it's now the #1 Google search result for react native call component method, which is how I ended up here myself. Some of the details here may be confusing for the casual passer-by, and I'd like to add two quick clarifications:

  1. ReactJS is actually agnostic about this technique. I just went back through the docs on refs and ref usage and other than one caution against overuse, at no time is this specifically forbidden or referred to there as an anti-pattern. In fact, the more complex use case, where refs are dealt with through multiple layers of parent->child calls, has an entire documentation page (Forwarding Refs) describing how to do it properly and why you might want it.

  2. The closely related React Native library EXTENSIVELY uses this method to provide Component-level APIs (https://facebook.github.io/react-native/docs/webview#goback, among many dozens of examples). If you are using React as part of React Native, this is absolutely not an antipattern there - the core devs use it and document it as a standard technique.

Many of the suggestions above for working around this method are useful to know. I don't personally agree that a much longer solution or library dependency to avoid a 2-line option is objectively "better", or that this option (direct ref-taking) is indeed even a true "antipattern," but that's my personal opinion. I respect the authors of this starter kit, their work, and their goals for how it's meant to be used. My comments here apply only to those looking for this solution generally (particularly in React Native, as I was before I went on this fact-checking goose chase), NOT in the context of React Starter Kit.

theCuriousOne commented 6 years ago

Let me add some more advice to the pour soul that might be having some issue with the referencing of child component.

THE REF DOES NOT WORK IF THE CHILD COMPONENT IS BEING LAZY LOADED!

I lost almost 2 days before realizing that. After i removed the react-loadable (the npm package that makes any react component lazy loadable), the referencing worked nicely like mentioned by several people above

theRafaelThinassi commented 5 years ago

Based on @joeytwiddle solution, i've implemented this another solution, who have worked very fine.

class Parent extends Component {
    render() {
      return (
        <div>
          {/* Give the child a callback, so it can share its methods with us. */}
          {React.cloneElement(this.props.children, {onChildDoAlert: _handleChildDoAlert => {this.childDoAlert = _handleChildDoAlert}})}
          {/* We can now call the child's method directly! */}
          <button onClick={this.childDoAlert}>Click</button>
        </div>
      );
    }
  }

  class Child extends Component {
    componentDidMount() {
        // Child passes its method to the parent
        this.props.onChildDoAlert(this.handleDoAlert.bind(this))
        // or you can pass the implementation directly
        // this.props.onChildDoAlert(() => {
        //     alert('clicked')
        // })
    }
    handleDoAlert() {
      alert('clicked');
    }
    render() {
      return (
        <h1>Hello</h1>
      );
    }
  }
YisraelV commented 5 years ago

Here is an example why I think this is not an antipattern:

You have a panel component that can be in an "folded" or "unfolded" state. The component includes a button to fold\unfold.

Now you have a parent component with several of such panels. You have a buttons in parent "fold all" and "unfold all" which cause all the accordions to fold\unfold.

You could pass a "open=false" as a prop. The problem is now you have two sources of truth: the parent's (which is passed as a prop) and the child's (which is changed with setState on every click)

What you might do is make the parent the single source of truth, and on every click of the child call "onClick" callback and have the parent modify the state.

That might work but is not intuitive. Why should the child tell the parent it was clicked just so the parent can tell it again "yes you were clicked" using a prop? Intuitively the child owns the state here. And intuitive app is more readable and understandable.

On the other hand changing the child's state using a method conveys the message: the child owns the state here, and it allows others to ask him to change.

mfaheemakhtar commented 5 years ago

I have not read everything but I was facing the same issue. Instead of using refs. I used callback.

I had to reset the form fields once the container has submitted the form.

Child:

validateFields((err, values) => {
    // handleSubmit is from the parent (props)
    handleSubmit(err, values, () => {
        form.resetFields();
    });
});

Parent:

handleSubmit = (err, values, resetFields) => {
    // my code
    resetFields();
}

Anti-Pattern?

yairEO commented 5 years ago

@langpavel - Your explanation of why this is an anti-pattern, and suggesting to use Redux is not satisfactory.

React should handle such basic needs without the needs of an external independent tool such as Redux.

Components are instances of javascript classes (or functions) and there should be a "reactive" way to simply access the instances' methods from the parent, in React.

For example:

I have N stopwatches on the screen and one button to reset them all. Each timer has its own state, and methods (start, stop, restart), each timer works independently.

I believe passing reset prop to the timers is just bad programming, a thing to be triggered, while a "prop" is more of a "permanent" state, until it changes. having a timeout to set the "restart" prop back to false is wrong.

I ended up with a restarts prop which is a an integer being increased on every "restart" event, and had set up a useEffect which listens to changes to the restarts prop and acts, but I would honestly prefer not working with props for such things. Feels wrong.

Back in the good days, in vanilla JS, I could easily create an instance of a timer from a parent, and render it using my vanilla framework, and from the parent component loop on all the child instances which are timers and call each of those restart methods.


Why, for such a simple app (described above), would I need Redux or RXJX for? it's insane.

vpillinger commented 5 years ago

@yairEO The issue is less that using refs is an anti-pattern and more that refs are just unnecessarily difficult to work with.

You can write 2-3 lines of code and pass a silly callback or you can write 15-20 lines of code to track the refs and call the child function. This gets super annoying when your adding/removing elements. Imagine you were writing a dashboard with custom blocks that could be customized, but had to refresh every 30 seconds to update their data. However, you also needed a manual refresh button. What if that refresh button could also be moved around the page?

Trying to track all the refs down into the dashboard would be a nightmare. It may be simple enough, but if you allow components to be nested then... 💥.

This is very similar to your use case with the timers and it really shows where React falls short. It does not provide a built-in observer pattern.

I would actually argue that the callback method is the anti-pattern since its a convoluted replacement of observer pattern. Ultimately, the issue is that React doesn't provide a way to fire/listen-to custom events which is something that even simple apps have the need for very quickly if you don't want to re-glue all your components every time you move something around on the page.

What your really looking for is something like this: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events or something like what Vue does https://vuejs.org/v2/guide/components-custom-events.html. How this works is to fire a "refresh" event and observe it with the individual timers who are responsible for handling the event appropriately. Which is slightly more code than the callback method but 1) Is actually understandable by a normal human without having to explain it to them and draw 3 diagrams explaining how we create a function to pass a function to pass a function to finally call the function that calls the last function. 2) It completely decouples the desired behavior from the component hierarchy whereas passing props, callbacks, and using refs doesn't solve the core issue. 3) Its unit testable 4) Its a pattern every programmer with a modicum of experience should know and be familiar with. Without including Redux, flux, or any other library which in my opinion are a waste of time outside of very specific use-cases. Disclaimer, I have never used Redux, but I also never found the need to create a god class in order to implement event management.

strommj commented 5 years ago

Another solution: Ditch react and use Angular, haha. You'll have all the state control you'll ever need brought to you by mutable classes!

In all seriousness, thanks Frenzzy for the great solution! I'm looking for a way to solve a very simple problem: I have a button in the parent container, and I have a menu that needs to be toggled in a child component once that parent button is clicked. For this I'll need to pass some sort of data down to the child, or in this solution, just call the child listener function to toggle the Boolean state property.

Or another simpler way of putting it: I'm making a website navigation bar. (When you click the hamburger icon on a mobile device, and the menu drops down).

lukewlms commented 4 years ago

We use a custom hook called useCounterKey to send events to children. Although it's not as straightforward as e.g. a simple HTML event handler, it's less messy than using refs (the data flow is only one-way) and is pretty React-idiomatic (since incrementing keys can already be used to reset components).

Details here on how we're using it: https://stackoverflow.com/a/60568459/152711

sli782 commented 4 years ago

I found a reasonably sensible solution to this problem by 'brickingup' on StackOverflow.

The idea is that the parent passes a callback function to the child, and the child calls that function, to pass its method(s) to the parent. The parent can then store the methods locally.

The important code is commented below.

class Parent extends Component {
  acceptMethods(childDoAlert) {
    // Parent stores the method that the child passed
    this.childDoAlert = childDoAlert;
  }
  render() {
    return (
      <div>
        {/* Give the child a callback, so it can share its methods with us. */}
        <Child shareMethods={this.acceptMethods.bind(this)}/>
        {/* Later we can call the child's method directly! */}
        <button onClick={() => this.childDoAlert()}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  componentDidMount() {
    // Child passes its method to the parent
    this.props.shareMethods(this.doAlert.bind(this));
  }
  doAlert() {
    alert('clicked');
  }
  render() {
    return (
      <h1>Hello</h1>
    );
  }
}

If you want to receive more than one method from the child, then change shareMethods and acceptMethods to pass an object instead of just one function.

For clarity, I think the acceptMethods function should be placed as close to the constructor as possible.

Thanks!!!

dfyz011 commented 3 years ago

Example with functional components and useRef hook:

/ Child.jsx /

import React, { useEffect } from "react";
import withStyles from "isomorphic-style-loader/lib/withStyles";
import s from "./Child.css";

const Child = () => {
  useEffect(() => {
    onRef(() => {
      console.log("lol");
    });
  }, []);
  return <h1 className={s.root}>Hello World!</h1>;
};

export default withStyles(s)(Child);

/ Parent.jsx /

import React, { useRef } from "react";
import Child from "./Child";

const callbackRef = useRef();

const Parent = () => {
  return (
    <div>
      <Child
        onRef={(callback) => {
          callbackRef.current = callback;
        }}
      />
      <button
        onClick={() => {
          callbackRef && callbackRef.current();
        }}
      >
        Child.method()
      </button>
    </div>
  );
};
mattgle commented 1 year ago

The answer from @dfyz011 works but not in TypeScript, here's the TS option, hope it helps 😄

/ Child.tsx /

import React, { useEffect } from "react";

type Props = {
   onRef: (callback: Function) => void;
 }

export const Child: React.FC<Props> ({onRef}) => {
  useEffect(() => {
    onRef(() => {
      console.log("Dude, see? It's working!");
    });
  }, [onRef]);

  return <h1 className={s.root}>Hello World!</h1>;
};

/ Parent.tsx /

import React, { useRef } from "react";
import Child from "./Child";

export const Parent = () => {
  const callbackRef = useRef<Function>();
  const handleOnRef = (callback: Function) => {
    callbackRef.current = callback;
  };

  return (
    <div>
      <Child onRef={handleOnRef} />
      <button
        onClick={() => {
          callbackRef?.current?.();
        }}
      >
        Child.method()
      </button>
    </div>
  );
};