5

I have a child component that need to listen to one of it's parent event. More precisely, I have a function in the child component that takes as parameter an event from the parent. I would like to call this function everytime the event occurs.

As an example, here is a code snippet:

class Parent extends React.Component {
   handleKeyDown = (event) => {
      // Call the child function doSomething()
   }

   render() {
      return (
         <input
            type="text"
            onKeyDown={this.handleKeyDown}
         >

         <Child />
      )
   }
}
class Child extends React.Component {
   doSomething = (event) => {
      // Get the event from parent
   }

   render() {
      return (
         ...
      )
   }
}

I have considered two ways to do it:

  • Using ref to call the child function from the parent onKeyDown (supposing that I can access it)
  • Using a state to store the event and pass it as a props to the child, then listen to props changes with getDerivedStateFromProps.

However, none of these solutions seems very appealing. I have also thought about using a redux function but I need data from the child component as well as the event from the parent component... I was wondering if there is a clean way do to that?

Arkellys
  • 3,458
  • 2
  • 12
  • 31
  • Using a prop would be the way I would do it. Refs should always be a last resort. – JavanPoirier Apr 01 '19 at 13:47
  • It is better to move `doSomething` and relevant child data to `Parent`. If you really need to have `doSomething` inside `Child` component then `ref` is the best option. – UjinT34 Apr 01 '19 at 14:01
  • @UjinT34 Luckily the child component is very small and is not meant to be used elsewhere. I decided to put it separately because it was cleaner (the parent component already has a lot of lines). I think that I will move the method and data only if I have no other choice but I am very surprised that React don't have any built-in solutions for a situation like this. – Arkellys Apr 01 '19 at 14:09

3 Answers3

6

Update:

I updated my components to use hooks and ended up using useRef(), useImperativeHandle() and forwardRef() to handle this case:

const Parent = () => {
   const childRef = useRef();

   const handleKeyDown = (event) => {
      // Call the child function doSomething()
      childRef.current.doSomething(event);
   };
   
   return (
      <input
         type="text"
         onKeyDown={handleKeyDown}
      >
    
      <Child ref={childRef} />
   );
};
const Child = forwardRef((props, ref) => {
   useImperativeHandle(ref, () => ({
      doSomething: (event) => {
         // Get the event from parent
      }
   }));

   return (
      [...]
   );
});


I decided to use the solution provided by Francis Malloch on this post1:

class Parent extends React.Component {
   childCallables = null;
    
   setChildCallables = (callables) => {
      this.childCallables = callables;
   }
    
   handleKeyDown = (event) => {
      // Call the child function doSomething()
      this.childCallables.doSomething(event);
   }
    
   render() {
      return (
         <input
            type="text"
            onKeyDown={this.handleKeyDown}
         >
    
         <Child setCallables={this.setChildCallables} />
      )
   }
}
class Child extends React.Component {
   componentDidMount() {
      this.props.setCallables({
         doSomething: this.doSomething
      });
   }
    
   doSomething = (event) => {
      // Get the event from parent
   }
    
   render() {
      return (
         [...]
      )
   }
}

Basically, I'm using a props to store the child's methods I need to access from the parent. The methods are saved in the props just after the child component is mounted.


1. Since it is an answer to a completely different question, I don't think marking this one as a duplicate would make sense.

Arkellys
  • 3,458
  • 2
  • 12
  • 31
2

You can write a HOC like this:

const withChild = Wrapped => class Child extends React.Component {
   doSomething = (event) => {

   }

   render() {
      return (
         <React.Fragment>
            <Wrapped {...this.props} onKeyDown={this.doSomething}/>
            whatever Child should render
         </React.Fragment>
      )
   }
}

const ParentWithChild = withChild(class Parent extends React.Component {
   handleKeyDown = (event) => {
      // Call the child function doSomething()
      if (typeof(this.props.onKeyDown) === 'function') {
          this.props.onKeyDown(event);
      }
   }

   render() {
      return (
         <input
            type="text"
            onKeyDown={this.handleKeyDown}
         >
      )
   }
});
UjinT34
  • 4,618
  • 1
  • 12
  • 26
  • Thank you for your answer, I didn't know about HOC. Unfortunately, I don't think I will use this structure, it looks to complicated for what I want to do. – Arkellys Apr 04 '19 at 07:44
0

Try calling doSomething in render method before returning on the basis of props changed but this will result in an infinite loop in case you are changing the state of child component in doSomething.

  • 1
    I need to call the method on a specific event, not every time a props change. – Arkellys Apr 01 '19 at 13:57
  • Yes I got it. I meant in your parent on event fire you can change state and pass the changed state (say Boolean true ) as a prop to child. In render method of child component you can check if the state is true and call doSomething. In doSomething, call a parent method to change the state back to false once you are done with the actions you want to perform. – Prerna Chuttani Apr 01 '19 at 14:01
  • I thought about this solution too, but it doesn't seem very clean either. :/ – Arkellys Apr 01 '19 at 14:11
  • That's right. Ideally parent should not be calling child methods, child should call parent methods. This was just a workaround in case it is important for you to call a child component method. – Prerna Chuttani Apr 01 '19 at 14:15