devrnt / react-use-intercom

☎️ React Intercom integration without the hassle, powered by hooks.
https://devrnt.github.io/react-use-intercom/#/useIntercom
MIT License
337 stars 43 forks source link

Calling shutdown one time lose all the IntercomContext reactivity until the page is refreshed #666

Closed amodiopescefaro closed 5 months ago

amodiopescefaro commented 9 months ago

Describe the bug Calling shutdown() will disconnect the Intercom callbacks. Calling boot again to re-connect will not re-attach them. So everything will works but the callback will not be called anymore

A usecase is the user opting out of cookies for example and then re-opting in in the same session.

To Reproduce

  1. Wrap your code in the IntercomProvider context
  2. Have some logic call boot, then shutdown, then boot again What happens: All the reactivity of the IntercomProvider context is lost:
    • isOpen will not update anymore when the messanger is opened/closed
    • all the callbacks attached to the context will not be called anymore

Codesandbox link https://codesandbox.io/p/sandbox/react-new

Expected behavior Calling boot should re-connet all the callbacks

Fix Suggestions This block of code in the IntercomProvider should be updated

if (!isSSR && shouldInitialize && !isInitialized.current) {
    initialize(appId, initializeDelay);

    // attach listeners
    IntercomAPI('onHide', onHideWrapper);
    IntercomAPI('onShow', onShowWrapper);
    IntercomAPI('onUserEmailSupplied', onUserEmailSupplied);

    if (onUnreadCountChange)
      IntercomAPI('onUnreadCountChange', onUnreadCountChange);

    if (autoBoot) {
      boot(autoBootProps);
    }

    isInitialized.current = true;
  }
  1. no action code should go in the body of a component. This initialization should probably go inside a useEffect
  2. the listeners attachments should be moved inside the "boot" function, as any shutdown call will disconnect them they need to be re-connected at every boot
// Attach callbacks on boot
const boot = React.useCallback(
    (props?: IntercomProps) => {
      if (!window.Intercom && !shouldInitialize) {
        logger.log(
          'warn',
          'Intercom instance is not initialized because `shouldInitialize` is set to `false` in `IntercomProvider`',
        );
        return;
      }

      const metaData: RawIntercomBootProps = {
        app_id: appId,
        ...(apiBase && { api_base: apiBase }),
        ...(props && mapIntercomPropsToRawIntercomProps(props)),
      };

      window.intercomSettings = metaData;
      IntercomAPI('boot', metaData);

      // attach listeners
      IntercomAPI('onHide', onHideWrapper);
      IntercomAPI('onShow', onShowWrapper);
      IntercomAPI('onUserEmailSupplied', onUserEmailSupplied);

      if (onUnreadCountChange)
        IntercomAPI('onUnreadCountChange', onUnreadCountChange);

      isBooted.current = true;
    },
    [apiBase, appId, shouldInitialize, onHideWrapper, onShowWrapper, onUserEmailSupplied, onUnreadCountChange],
  );

// Move inizialization in an effect run only on mount
useEffect(() => {
  if (shouldInitialize && !isInitialized.current) {
    initialize(appId, initializeDelay);
    isInitialized.current = true;
    if (autoBoot) {
      boot(autoBootProps);
    }
  }
}, []);
devrnt commented 6 months ago

Thanks for reporting. I'll look into it

devrnt commented 5 months ago

5.4.1