Refs in React

· 3 min read

Refs are quite commonly used in React - they enable parent components to manipulate child components/native DOM, such as controlling a modal component popup display, etc. But sometimes you can encounter pitfalls when using them that are easy to overlook. Let’s organize these here.

  1. After ref successfully mounts an element, it does not trigger component re-rendering

    Sometimes you might add ref or ref.current to useEffect dependencies, but actually when the component referenced by the ref is mounted, although ref.current already holds the instance reference, the component won’t trigger re-rendering, let alone the effect re-executing. Therefore, when encountering such requirements, you need to modify the solution. Methods are as follows:

  2. For ref usage, you can directly assign ref objects, or use callbacks. Both methods serve the same purpose, with the only difference being that if you want to perform some actions when mounting, you can use callbacks. Here’s an example:

    function App() {
      const pElRef = useRef<any>();
      const [refresh, setRefresh] = useState(0);
      useEffect(() => {
        console.log('render?');
      }, [refresh]);
    
      const onAttached = useCallback((ref) => {
        if (refresh === 0) {
          setRefresh(refresh + 1);
        }
        pElRef.current = ref;
      }, [refresh]);
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo"/>
            <p ref={onAttached}>
              Edit <code>src/App.tsx</code> and save to reload.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
        </div>
      );
    }
    

​ As shown above, add a state parameter refresh and modify it when mounting succeeds to achieve re-rendering.

Ref Forwarding in HOC

When using components, there are several special parameters, such as key and ref. They don’t belong to props - React handles them specially, so you need to be careful in HOC scenarios.

Here’s an example: in the following code, the App component hopes to use SayRef to manipulate the User component, but the User component isn’t directly provided to App for consumption - it’s wrapped by HOC. Here, when props are passed to User, ref won’t be forwarded. The ref reference will point to SayHoc, so clicking here won’t work.

export function SayHoc({ boss }: { boss: string }) {
  return (props: any) => {
    return <h1>
      from:{boss} say hello:
      <User {...props}/>
    </h1>;
  };
}


const User = React.forwardRef((props: { name: string }, ref) => {
  useImperativeHandle(ref, () => {
    return ({
      say: () => alert(props.name),
    });
  });
  return <>{props.name}</>;
});


const Say = SayHoc({ boss: 'L' });

function App() {
  const sayRef = useRef<any>();
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo"/>
        <Say name={'2121'} ref={sayRef}/>
        <button onClick={() => {
          sayRef.current?.say();
        }}>
          say alert
        </button>
      </header>
    </div>
  );
}

export default App;

The solution is as follows, using React.forwardRef.

export function SayHoc({ boss }: { boss: string }) {
  return React.forwardRef((props: any, ref) => {
    return <h1>
      from:{boss} say hello:
      <User ref={ref} {...props}/>
    </h1>;
  });
}