useEffect for an event listener to scroll the page - works as PWA but not on iOS

I have a simple event listener that automatically scrolls to a <details> element when the element is clicked.

I’m using useEffect(); this works when I test it as a PWA in Chrome, but when I build the app for iOS, the listener randomly fails a lot. I don’t see it fail at all in Windows Chrome.

When I check the debug log, the listener is being attached to the wrong <details> element (a <details> element that is not being displayed.

The component is shown one-by-one in a queue of items; I don’t understand why it always works on desktop but not on mobile.

  const scrollAnswerIntoView = () => {
    const showAnswer = document.getElementById('show-answer');
    if (showAnswer) {
      showAnswer.scrollIntoView();
    }
  };

  useEffect(() => {
    const detailsAnswer = document.querySelector('details#show-answer');
    detailsAnswer?.addEventListener('toggle', scrollAnswerIntoView);
    return () => {
      detailsAnswer?.removeEventListener('toggle', scrollAnswerIntoView);
    };
  }, []);

  return (
    <details id="show-answer">
      <summary>
        Hey
      </summary>
      <br />
      { children }
    </details>
  );

How can I write such an event listener that works on both desktop and iOS?

I referred to this question, and I tried using useIonViewWillEnter() instead, but I’m getting the same problem on iOS (stale data). I was wondering what’s the “best practice” for adding an event listener in Ionic React.

I fixed this by adding a unique ID to each of my details elements instead of reusing the same ID. This fixed the problem in iOS and also works on the web.

Update: Unfortunately, this does not work in Android. The reason is that garbage collection appears to be different in Android, and Android may keep the component in memory.

In my case, specifically, when I navigate from one tab to another, the original tab is still kept in memory, so when I return to that tab, the useEffect() doesn’t fire, but the event listener is gone (perhaps garbage collected by Android OS?) So I am still searching for a solution that works on all three platforms.

  const saId = `sa-${did}`;

  useEffect(() => {
    const detailsAnswer = document.querySelector(`details#${saId}`);
    const scrollAnswerIntoView = () => {
      const showAnswer = document.getElementById(saId);
      // console.log('show answer check', showAnswer);
      if (showAnswer) {
        showAnswer.scrollIntoView();
      }
    };
    detailsAnswer?.addEventListener('toggle', scrollAnswerIntoView);
    // console.log('useEffect listener for sa', detailsAnswer);
    return () => {
      detailsAnswer?.removeEventListener('toggle', scrollAnswerIntoView);
    };
  }, [saId]);

  return (
    <details id={csaId} className="show-answer">
      <summary>
        Hey
      </summary>
      <br />
      { children }
    </details>
  );

Id always has to be unique on a page !
But for you case you should use react ref and not linked to an id, will be more react frendly from my opinion…

Thanks. I was confused because I thought that each <IonContent> created a new “page”, so it would be OK to have the same ID in different <IonContent> because there is only one <IonContent> per <IonPage>. However, it seems that if the <IonPage> is kept in memory, then having the same ID in multiple <IonContent> can lead to errors like I experienced.