Call child method from parent

前端 未结 16 2100
长发绾君心
长发绾君心 2020-11-21 22:23

I have two components.

  1. Parent component
  2. Child component

I was trying to call child\'s method from Parent, I tried this way but couldn\

相关标签:
16条回答
  • 2020-11-21 23:16

    If you are doing this simply because you want the Child to provide a re-usable trait to its parents, then you might consider doing that using render-props instead.

    That technique actually turns the structure upside down. The Child now wraps the parent, so I have renamed it to AlertTrait below. I kept the name Parent for continuity, although it is not really a parent now.

    // Use it like this:
    
      <AlertTrait renderComponent={Parent}/>
    
    
    class AlertTrait extends Component {
      // You will need to bind this function, if it uses 'this'
      doAlert() {
        alert('clicked');
      }
      render() {
        return this.props.renderComponent({ doAlert: this.doAlert });
      }
    }
    
    class Parent extends Component {
      render() {
        return (
          <button onClick={this.props.doAlert}>Click</button>
        );
      }
    }
    

    In this case, the AlertTrait provides one or more traits which it passes down as props to whatever component it was given in its renderComponent prop.

    The Parent receives doAlert as a prop, and can call it when needed.

    (For clarity, I called the prop renderComponent in the above example. But in the React docs linked above, they just call it render.)

    The Trait component can render stuff surrounding the Parent, in its render function, but it does not render anything inside the parent. Actually it could render things inside the Parent, if it passed another prop (e.g. renderChild) to the parent, which the parent could then use during its render method.

    This is somewhat different from what the OP asked for, but some people might end up here (like we did) because they wanted to create a reusable trait, and thought that a child component was a good way to do that.

    0 讨论(0)
  • 2020-11-21 23:16

    Here my demo: https://stackblitz.com/edit/react-dgz1ee?file=styles.css

    I am using useEffect to call the children component's methods. I have tried with Proxy and Setter_Getter but sor far useEffect seems to be the more convenient way to call a child method from parent. To use Proxy and Setter_Getter it seems there is some subtlety to overcome first, because the element firstly rendered is an objectLike's element through the ref.current return => <div/>'s specificity. Concerning useEffect, you can also leverage on this approach to set the parent's state depending on what you want to do with the children.

    In the demo's link I have provided, you will find my full ReactJS' code with my draftwork inside's so you can appreciate the workflow of my solution.

    Here I am providing you my ReactJS' snippet with the relevant code only. :

    import React, {
      Component,
      createRef,
      forwardRef,
      useState,
      useEffect
    } from "react"; 
    
    {...}
    
    // Child component
    // I am defining here a forwardRef's element to get the Child's methods from the parent
    // through the ref's element.
    let Child = forwardRef((props, ref) => {
      // I am fetching the parent's method here
      // that allows me to connect the parent and the child's components
      let { validateChildren } = props;
      // I am initializing the state of the children
      // good if we can even leverage on the functional children's state
      let initialState = {
        one: "hello world",
        two: () => {
          console.log("I am accessing child method from parent :].");
          return "child method achieve";
        }
      };
      // useState initialization
      const [componentState, setComponentState] = useState(initialState);
      // useEffect will allow me to communicate with the parent
      // through a lifecycle data flow
      useEffect(() => {
        ref.current = { componentState };
        validateChildren(ref.current.componentState.two);
      });
    
    {...}
    
    });
    
    {...}
    
    // Parent component
    class App extends Component {
      // initialize the ref inside the constructor element
      constructor(props) {
        super(props);
        this.childRef = createRef();
      }
    
      // I am implementing a parent's method
      // in child useEffect's method
      validateChildren = childrenMethod => {
        // access children method from parent
        childrenMethod();
        // or signaling children is ready
        console.log("children active");
      };
    
    {...}
    render(){
           return (
              {
                // I am referencing the children
                // also I am implementing the parent logic connector's function
                // in the child, here => this.validateChildren's function
              }
              <Child ref={this.childRef} validateChildren={this.validateChildren} />
            </div>
           )
    }
    
    0 讨论(0)
  • 2020-11-21 23:20

    You can use another pattern here:

    class Parent extends Component {
     render() {
      return (
        <div>
          <Child setClick={click => this.clickChild = click}/>
          <button onClick={() => this.clickChild()}>Click</button>
        </div>
      );
     }
    }
    
    class Child extends Component {
     constructor(props) {
        super(props);
        this.getAlert = this.getAlert.bind(this);
     }
     componentDidMount() {
        this.props.setClick(this.getAlert);
     }
     getAlert() {
        alert('clicked');
     }
     render() {
      return (
        <h1 ref="hello">Hello</h1>
      );
     }
    }
    

    What it does is to set the parent's clickChild method when child is mounted. In this way when you click the button in parent it will call clickChild which calls child's getAlert.

    This also works if your child is wrapped with connect() so you don't need the getWrappedInstance() hack.

    Note you can't use onClick={this.clickChild} in parent because when parent is rendered child is not mounted so this.clickChild is not assigned yet. Using onClick={() => this.clickChild()} is fine because when you click the button this.clickChild should already be assigned.

    0 讨论(0)
  • 2020-11-21 23:21

    I hope I'm not repeating anything from above but what about passing a callback prop that sets the function in the parent? This works and is pretty easy. (Added code is between the ////'s)

    class Parent extends Component {
      ///// 
      getAlert = () => {} // initial value for getAlert
    
      setGetAlertMethod = (newMethod) => {
        this.getAlert = newMethod;
      }
      /////
    
      render() {
        return (
          <Child setGetAlertMethod={this.setGetAlertMethod}>
            <button onClick={this.getAlert}>Click</button>
          </Child>
          );
        }
      }
    
    
    
    class Child extends Component {
      /////
      componentDidMount() {
        this.props.setGetAlertMethod(this.getAlert);
      }
      /////
    
      getAlert() => {
        alert('clicked');
      }
    
      render() {
        return (
          <h1 ref="hello">Hello</h1>
        );
      }
    }
    
    0 讨论(0)
  • 2020-11-21 23:21

    Another way of triggering a child function from parent is to make use of the componentDidUpdate function in child Component. I pass a prop triggerChildFunc from Parent to Child, which initially is null. The value changes to a function when the button is clicked and Child notice that change in componentDidUpdate and calls its own internal function.

    Since prop triggerChildFunc changes to a function, we also get a callback to the Parent. If Parent don't need to know when the function is called the value triggerChildFunc could for example change from null to true instead.

    const { Component } = React;
    const { render } = ReactDOM;
    
    class Parent extends Component {
      state = {
        triggerFunc: null
      }
    
      render() {
        return (
          <div>
            <Child triggerChildFunc={this.state.triggerFunc} />
            <button onClick={() => {
              this.setState({ triggerFunc: () => alert('Callback in parent')})
            }}>Click
            </button>
          </div>
        );
      }
    }
    
    class Child extends Component {
      componentDidUpdate(prevProps) {
        if (this.props.triggerChildFunc !== prevProps.triggerChildFunc) {
          this.onParentTrigger();
        }
      }
    
      onParentTrigger() {
        alert('parent triggered me');
    
        // Let's call the passed variable from parent if it's a function
        if (this.props.triggerChildFunc && {}.toString.call(this.props.triggerChildFunc) === '[object Function]') {
          this.props.triggerChildFunc();
        }
      }
    
      render() {
        return (
          <h1>Hello</h1>
        );
      }
    }
    
    
    render(
      <Parent />,
      document.getElementById('app')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    <div id='app'></div>

    CodePen: https://codepen.io/calsal/pen/NWPxbJv?editors=1010

    0 讨论(0)
  • 2020-11-21 23:22

    Alternative method with useEffect:

    Parent:

    const [refresh, doRefresh] = useState(0);
    <Button onClick={() => doRefresh(prev => prev + 1)} />
    <Children refresh={refresh} />
    

    Children:

    useEffect(() => {
        performRefresh(); //children function of interest
      }, [props.refresh]);
    
    0 讨论(0)
提交回复
热议问题