Uzair Khan
Uzair Khan

Reputation: 2970

Wait for DOM element then react - Reactjs

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

Upvotes: 8

Views: 22766

Answers (6)

Onat
Onat

Reputation: 801

Here's my hook version to @hao-wu's answer

const useGetElementAsync = (query) => {
  const [element, setElement] = useState(null);

  useEffect(() => {
    (async () => {
      let element = await new Promise((resolve) => {
        function getElement() {
          const element = document.querySelector(query);
          if (element) {
            resolve(element);
          } else {
            console.count();
            // Set timeout isn't a must but it
            // decreases number of recursions
            setTimeout(() => {
              requestAnimationFrame(getElement);
            }, 100);
          }
        };

        getElement();
      });

      setElement(element);
    })();
  }, [query]);

  return element;
};

To use it:

export default function App() {
  const [isHidden, setIsHidden] = useState(true);
  const element = useGetElementAsync(".myElement");

  // This is to simulate element loading at a later time
  useEffect(() => {
    setTimeout(() => {
      setIsHidden(false);
    }, 1000);
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      {!isHidden && (
        <h2 className="myElement">My tag name is {element?.tagName}</h2>
      )}
    </div>
  );
}

Here's the codesandbox example

Upvotes: 2

Andrii Los
Andrii Los

Reputation: 556

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.

Upvotes: 0

Bhojendra Rauniyar
Bhojendra Rauniyar

Reputation: 85573

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
  }
}

Upvotes: 0

Hao Wu
Hao Wu

Reputation: 20867

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
  }
}

Upvotes: 11

T.J. Crowder
T.J. Crowder

Reputation: 1075199

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.)

Upvotes: 18

Adeel Imran
Adeel Imran

Reputation: 13966

You can do something like;

componentDidMount() {
  // Triggering load of some element
  document.querySelector("#id").onload = function() {
    // Write your code logic here
    // code here ..
  }
}

Reference https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload

Upvotes: -1

Related Questions