reactjsreact-hookscreate-ref

Give ref to functional component to use inner functions


I have a Component let's say:

ParenComp.js

const ParentComp = (props) => {
  const [isTrue, setIsTrue] = useState(false);
  const setToTrue = () => {
    setIsTrue(true);
  };
  const setToFalse = () => {
    setIsTrue(false);
  };
  return isTrue ? (
  
      <Text >
        This is True
      </Text>
    
  ) : (
       <Text >
        This is False
      </Text>
  );
};
export default ParentComp;

Main Question

How can I use the setToTrue and setToFalse function in other functional component in any other file for example (Login.js)?

What I tried

I want to use the inner functions in another file, I know I can not export the functions like this:

export const setToTrue = () => { setIsTrue(true); };

^ This is invalid

But what I was trying to do is (in ParentComp.js) create a reference using createRef, export it and create and export two functions that call the inside functions like this:

export const trueRef = React.createRef();
export function setToTrue() {
  let ref = trueRef.current;
  if (ref) {
    ref.setToTrue();
  }
}

export function setToFalse() {
  let ref = trueRef.current;
  if (ref) {
    ref.setToFalse();
  }
}

Now when I want to use this in my (Login.js). This is what I do:

const Login = ({ navigation }) => {
return (
<View>
<ParentComp ref={trueRef}/>
</View>
)
}

But the problem is, ref is not being passed to ParentComp here

<ParentComp ref={trueRef}/>

So, without using CLass Components, how can I pass ref to my functional component to utilize the functions inside it?


Solution

  • Use the useImperativeHandle hook with ref forwarding to give an external component access to the methods.

    As noted by @technophyle and @Drew Reese in the comments, useImperativeHandle is an escape hatch that is usually used in specific cases that require direct access to a DOM node. For example, focusing an input, or scrolling to an element.

    Example:

    const { forwardRef, useState, useImperativeHandle, useRef } = React;
    
    const ParentComp = forwardRef((props, ref) => {
      const [isTrue, setIsTrue] = useState(false);
      const setToTrue = () => { setIsTrue(true); };
      const setToFalse = () => { setIsTrue(false); };
      
      useImperativeHandle(ref, () => ({
        setToTrue,
        setToFalse
      }));
      
      return (
        <div>This is {isTrue ? 'True' : 'False'}</div>
      );
    });
    
    const Login = () => {
      const trueRef = useRef();
      
      return (
        <div>
          <ParentComp ref={trueRef}/>
          <button onClick={() => trueRef.current.setToTrue()}>True</button>
          <button onClick={() => trueRef.current.setToFalse()}>False</button>
        </div>
      );
    };
      
    ReactDOM
      .createRoot(root)
      .render(<Login />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    A better way to reuse the logic, is to create a custom hook that encapsulates the behavior and use it in Login:

    const { useState } = React;
    
    const useIsTrue = () => {
      const [isTrue, setIsTrue] = useState(false);
      
      return {
        setToTrue() { setIsTrue(true); },
        setToFalse() { setIsTrue(false); },
        isTrue
      };
    }
    
    const ParentComp = ({ isTrue }) => (
      <div>This is {isTrue ? 'True' : 'False'}</div>
    );
    
    const Login = () => {
      const { setToTrue, setToFalse, isTrue } = useIsTrue();
      
      return (
        <div>
          <ParentComp isTrue={isTrue}/>
          <button onClick={setToTrue}>True</button>
          <button onClick={setToFalse}>False</button>
        </div>
      );
    };
      
    ReactDOM
      .createRoot(root)
      .render(<Login />);
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    
    <div id="root"></div>