Wait for DOM element then react - Reactjs

Viewed 7768

Hi I have a class component as shown below:

class SomeComponent extends Component {
    componentDidMount = () => {
       const divElement = document.getElementbyId('id'); // this element could take a few seconds to load
       if (props.something1 && props.something2) {
          ..do something with divElement's width
       }
    }
    render() {
      return ....
    }
}

I want to wait until divElement is loaded, or trigger an event when divElement is loaded so I can do my calculation later, tried adding setTimeout which did not work

5 Answers

Two answers for you:

Use a ref (if your component renders the element)

If the element is rendered by your component, use a ref.

Use a MutationObserver (if the element is outside React)

If the element is completely outside the React part of your page, I'd look for it with getElementById as you are, and if you don't find it, use a MutationObserver to wait for it to be added. Don't forget to remove the mutation observer in componentWillUnmount.

That would look something like this:

componentDidMount = () => {
   const divElement = document.getElementbyId('id');
   if (divElement) {
      this.doStuffWith(divElement);
   } else {
      this.observer = new MutationObserver(() => {
         const divElement = document.getElementbyId('id');
         if (divElement) {
            this.removeObserver();
            this.doStuffWith(divElement);
         }
      });
      this.observer.observe(document, {subtree: true, childList: true});
   }
}
componentWillUnmount = () => {
    this.removeObserver();
}
removeObserver = () => {
    if (this.observer) {
        this.observer.disconnect();
        this.observer = null;
    }
}

(You may have to tweak that, it's off-the-cuff; see the MutationObserver documentation for details.)

This is a dumb solution but it gets its jobs done:

const getElementByIdAsync = id => new Promise(resolve => {
  const getElement = () => {
    const element = document.getElementById(id);
    if(element) {
      resolve(element);
    } else {
      requestAnimationFrame(getElement);
    }
  };
  getElement();
});

To use it:

componentDidMount = async () => {
  const divElement = await getElementByIdAsync('id');
  if (props.something1 && props.something2) {
    // ..do something with divElement's width
  }
}

You need to use the componentDidUpdate hook instead of componentDidMount hook. And better to use ref rather than getting div element by it's id:

componentDidUpdate() {
  if (props.something1 && props.something2) {
    // use divElementRef to interact with
  }
}

The answer of @hao-wu is great. Just if anyone wonders how to use it with hooks here is my snippet.

const Editor = () => {
  const [editor, setEditor] = useState<SimpleMDE | null>(null);

  useEffect(() => {
    (async () => {
      const initialOptions = {
        element: await getElementByIdAsync(id),
        initialValue: currentValueRef.current
      };
      setEditor(
        new SimpleMDE({
          element: await getElementByIdAsync(id),
          initialValue: currentValueRef.current
        })
      );
    })();
  }, [id]);

  // Other effects that are looking for `editor` instance

  return <textarea id={id} />;
};

Otherwise, the constructor of SimpleMDE cannot find an element and everything is broken :) I guess you can adjust it to your use-case quite easily.

Most of the time useRef just works, but not in this scenario.

Related