Leo Messi
Leo Messi

Reputation: 6166

Convert from getElementById to useRef in React

There is a functional component implemented with getElementById and it needs to be updated to use useRef hook.

Original code:

import React, { useState, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';

const MyComponent: React.FunctionComponent<MyComponentProps> = ({
  myDefaultElement = 'default-element',
}) => {
  const [elementOutlet, setElementOutlet] = useState<HTMLOListElement>();

  useEffect(() => {
    const refElement = document.getElementById(myDefaultElement) as HTMLOListElement;
 
    if (refElement) {
      setElementOutlet(refElement);
    }
  }, [myDefaultElement]);

  return createPortal(
    <li>
     // something
    </li>,
    elementOutlet
  );
};

the above code works fine, the only change that I made was the first line inside useEffect.

From:

const refElement = document.getElementById(myDefaultElement) as HTMLOListElement;

To:

const refElement = useRef(myDefaultElement) as HTMLOListElement;

When hover the new line it says:

React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function

Is there a way to make ref work fine inside useEffect?

Upvotes: 3

Views: 12510

Answers (1)

Dupocas
Dupocas

Reputation: 21317

You can't call useRef inside an effect, check the rule of hooks. Try something like this

const Component = () =>{
  const node = useRef(null)
  useEffect(() =>{
    // current will never be null here, since this effect will 
    // only run after the first mount and therefore after refs 
    // appending
    setElementOutlet(node.current)
  },[])

  return <div ref={node} />
}

Refs already have an stable signature ensured by react's life cycle, so you don't have to declare it as a dependency inside your effect.


Judging by your example you're only receiving an id as props not the node itself. You have two options:

  • Continue to receive the element id and calling getElementById (there is nothing wrong with that) and then append the mounted node inside a ref
const Component = ({ id }) =>{
  const node = useRef(document.getElementById(id))

  useEffect(() =>{
    const el = document.getElementById(id)
    if(el) node.current = el
  },[id])
}

  • Receive the entire ref as props
const ParentComponent = () =>{
  const node = useRef(null)
  return(
    <>
      <Component childRef={node} />
      <div ref={node} />
    </> 
  )
}

const Component = ({ childRef }) =>{
 ...
}

Greetings fellow olister.

Upvotes: 3

Related Questions