Call child method from parent

前端 未结 16 2111
长发绾君心
长发绾君心 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:04

    First off, let me express that this is generally not the way to go about things in React land. Usually what you want to do is pass down functionality to children in props, and pass up notifications from children in events (or better yet: dispatch).

    But if you must expose an imperative method on a child component, you can use refs. Remember this is an escape hatch and usually indicates a better design is available.

    Previously, refs were only supported for Class-based components. With the advent of React Hooks, that's no longer the case

    Using Hooks and Function Components (>= react@16.8)

    const { forwardRef, useRef, useImperativeHandle } = React;
    
    // We need to wrap component in `forwardRef` in order to gain
    // access to the ref object that is assigned using the `ref` prop.
    // This ref is passed as the second parameter to the function component.
    const Child = forwardRef((props, ref) => {
    
      // The component instance will be extended
      // with whatever you return from the callback passed
      // as the second argument
      useImperativeHandle(ref, () => ({
    
        getAlert() {
          alert("getAlert from Child");
        }
    
      }));
    
      return <h1>Hi</h1>;
    });
    
    const Parent = () => {
      // In order to gain access to the child component instance,
      // you need to assign it to a `ref`, so we call `useRef()` to get one
      const childRef = useRef();
    
      return (
        <div>
          <Child ref={childRef} />
          <button onClick={() => childRef.current.getAlert()}>Click</button>
        </div>
      );
    };
    
    ReactDOM.render(
      <Parent />,
      document.getElementById('root')
    );
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    
    <div id="root"></div>

    Documentation for useImperativeHandle() is here:

    useImperativeHandle customizes the instance value that is exposed to parent components when using ref.

    Using Class Components (>= react@16.4)

    const { Component } = React;
    
    class Parent extends Component {
      constructor(props) {
        super(props);
        this.child = React.createRef();
      }
    
      onClick = () => {
        this.child.current.getAlert();
      };
    
      render() {
        return (
          <div>
            <Child ref={this.child} />
            <button onClick={this.onClick}>Click</button>
          </div>
        );
      }
    }
    
    class Child extends Component {
      getAlert() {
        alert('getAlert from Child');
      }
    
      render() {
        return <h1>Hello</h1>;
      }
    }
    
    ReactDOM.render(<Parent />, document.getElementById('root'));
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    <div id="root"></div>

    Legacy API (<= react@16.3)

    For historical purposes, here's the callback-based style you'd use with React versions before 16.3:

    const { Component } = React;
    const { render } = ReactDOM;
    
    class Parent extends Component {
      render() {
        return (
          <div>
            <Child ref={instance => { this.child = instance; }} />
            <button onClick={() => { this.child.getAlert(); }}>Click</button>
          </div>
        );
      }
    }
    
    class Child extends Component {
      getAlert() {
        alert('clicked');
      }
    
      render() {
        return (
          <h1>Hello</h1>
        );
      }
    }
    
    
    render(
      <Parent />,
      document.getElementById('app')
    );
    <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="app"></div>

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

    I wasn't satisfied with any of the solutions presented here. There is actually a very simple solution that can be done using pure Javascript without relying upon some React functionality other than the basic props object - and it gives you the benefit of communicating in either direction (parent -> child, child -> parent). You need to pass an object from the parent component to the child component. This object is what I refer to as a "bi-directional reference" or biRef for short. Basically, the object contains a reference to methods in the parent that the parent wants to expose. And the child component attaches methods to the object that the parent can call. Something like this:

    // Parent component.
    function MyParentComponent(props) {
    
       function someParentFunction() {
          // The child component can call this function.
       }
    
       function onButtonClick() {
           // Call the function inside the child component.
           biRef.someChildFunction();
       }
    
       // Add all the functions here that the child can call.
       var biRef = {
          someParentFunction: someParentFunction
       }
    
       return <div>
           <MyChildComponent biRef={biRef} />
           <Button onClick={onButtonClick} />
       </div>;
    }
    
    
    // Child component
    function MyChildComponent(props) {
    
       function someChildFunction() {
          // The parent component can call this function.
       }
    
    
       function onButtonClick() {
          // Call the parent function.
          props.biRef.someParentFunction();
       }
    
       // Add all the child functions to props.biRef that you want the parent
       // to be able to call.
       props.biRef.someChildFunction = someChildFunction;
    
       return <div>
           <Button onClick={onButtonClick} />
       </div>;
    }
    

    The other advantage to this solution is that you can add a lot more functions in the parent and child while passing them from the parent to the child using only a single property.

    An improvement over the code above is to not add the parent and child functions directly to the biRef object but rather to sub members. Parent functions should be added to a member called "parent" while the child functions should be added to a member called "child".

    // Parent component.
    function MyParentComponent(props) {
    
       function someParentFunction() {
          // The child component can call this function.
       }
    
       function onButtonClick() {
           // Call the function inside the child component.
           biRef.child.someChildFunction();
       }
    
       // Add all the functions here that the child can call.
       var biRef = {
          parent: {
              someParentFunction: someParentFunction
          }
       }
    
       return <div>
           <MyChildComponent biRef={biRef} />
           <Button onClick={onButtonClick} />
       </div>;
    }
    
    
    // Child component
    function MyChildComponent(props) {
    
       function someChildFunction() {
          // The parent component can call this function.
       }
    
    
       function onButtonClick() {
          // Call the parent function.
          props.biRef.parent.someParentFunction();
       }
    
       // Add all the child functions to props.biRef that you want the parent
       // to be able to call.
       props.biRef {
           child: {
                someChildFunction: someChildFunction
           }
       }
    
       return <div>
           <Button onClick={onButtonClick} />
       </div>;
    }
    

    By placing parent and child functions into separate members of the biRef object, you 'll have a clean separation between the two and easily see which ones belong to parent or child. It also helps to prevent a child component from accidentally overwriting a parent function if the same function appears in both.

    One last thing is that if you note, the parent component creates the biRef object with var whereas the child component accesses it through the props object. It might be tempting to not define the biRef object in the parent and access it from its parent through its own props parameter (which might be the case in a hierarchy of UI elements). This is risky because the child may think a function it is calling on the parent belongs to the parent when it might actually belong to a grandparent. There's nothing wrong with this as long as you are aware of it. Unless you have a reason for supporting some hierarchy beyond a parent/child relationship, it's best to create the biRef in your parent component.

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

    We're happy with a custom hook we call useCounterKey. It just sets up a counterKey, or a key that counts up from zero. The function it returns resets the key (i.e. increment). (I believe this is the most idiomatic way in React to reset a component - just bump the key.)

    However this hook also works in any situation where you want to send a one-time message to the client to do something. E.g. we use it to focus a control in the child on a certain parent event - it just autofocuses anytime the key is updated. (If more props are needed they could be set prior to resetting the key so they're available when the event happens.)

    This method has a bit of a learning curve b/c it's not as straightforward as a typical event handler, but it seems the most idiomatic way to handle this in React that we've found (since keys already function this way). Def open to feedback on this method but it is working well!

    // Main helper hook:
    export function useCounterKey() {
      const [key, setKey] = useState(0);
      return [key, () => setKey(prev => prev + 1)] as const;
    }
    

    Sample usages:

    // Sample 1 - normal React, just reset a control by changing Key on demand
    function Sample1() {
      const [inputLineCounterKey, resetInputLine] = useCounterKey();
    
      return <>
        <InputLine key={inputLineCounterKey} />
        <button onClick={() => resetInputLine()} />
      <>;
    }
    
    // Second sample - anytime the counterKey is incremented, child calls focus() on the input
    function Sample2() {
      const [amountFocusCounterKey, focusAmountInput] = useCounterKey();
    
      // ... call focusAmountInput in some hook or event handler as needed
    
      return <WorkoutAmountInput focusCounterKey={amountFocusCounterKey} />
    }
    
    function WorkoutAmountInput(props) {
      useEffect(() => {
        if (counterKey > 0) {
          // Don't focus initially
          focusAmount();
        }
      }, [counterKey]);
    
      // ...
    }
    

    (Credit to Kent Dodds for the counterKey concept.)

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

    You can make Inheritance Inversion (look it up here: https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e). That way you have access to instance of the component that you would be wrapping (thus you'll be able to access it's functions)

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

    https://facebook.github.io/react/tips/expose-component-functions.html for more answers ref here Call methods on React children components

    By looking into the refs of the "reason" component, you're breaking encapsulation and making it impossible to refactor that component without carefully examining all the places it's used. Because of this, we strongly recommend treating refs as private to a component, much like state.

    In general, data should be passed down the tree via props. There are a few exceptions to this (such as calling .focus() or triggering a one-time animation that doesn't really "change" the state) but any time you're exposing a method called "set", props are usually a better choice. Try to make it so that the inner input component worries about its size and appearance so that none of its ancestors do.

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

    I'm using useEffect hook to overcome the headache of doing all this so now I pass a variable down to child like this:

    <ParentComponent>
     <ChildComponent arbitrary={value} />
    </ParentComponent>
    useEffect(() => callTheFunctionToBeCalled(value) , [value]);
    
    0 讨论(0)
提交回复
热议问题