7

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
  • 5,562
  • 2
  • 16
  • 40
  • 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
  • 1
    @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

10

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
  • 5,562
  • 2
  • 16
  • 40
  • Thanks man :) This is life saver for me. :) – rufatZZ Dec 22 '21 at 13:20
  • In this solution child component is tightly coupled here with the parent component. If we decide use some other child component we have to make sure that it has a function `doSomething` or update the parent component to call different function as per the new child component. In @ujint34 solution components are completly indipended and stands alone. – Dipak Jul 08 '22 at 14:37
3

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,784
  • 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
  • Prerna- one thing I don't understand about react is the point you just made. Meaning, how do you have logic in a component that another component uses if you can't tell the component w/the logic to do something? Otherwise, child components will have no logic and will just tell the parent to deal with it. How do you abstract logic so a parent doesn't become bloated with all this different logic for the components the parent uses? There must be an elegant way to hide logic that the parent can use but not know the dirty details – MattoMK May 11 '23 at 14:12
  • A good example. Say you have a dialog with a save button in the parent that handles the click. And that parent needs to validate its own data. But it uses a child component and it needs to know if the child's data is valid before sending the data to the server. Surely the parent shouldn't have to know how the validation works in the child. Just whether it's valid or not? Every child would be just simple markup and callbacks - and the parent will become increasingly bloated with all the handlers. How would you encapsulate the logic within the child that parent could use? – MattoMK May 11 '23 at 14:18