elgorditosalsero / react-gtm-hook

Easily manage the Google Tag Manager via Hook
https://elgorditosalsero-react-gtm-hook.netlify.app/
MIT License
220 stars 28 forks source link

useGTMHook mounts children components twice #16

Closed vladoavocado closed 3 years ago

vladoavocado commented 3 years ago

I don't know why but the <UseGTMHookProvider></UseGTMHookProvider> provider mounts children components two times:

  1. Mounts all children.
  2. Unmounts all children.
  3. Mounts them again.

I didn't expect the behavior to happen. Is it fine? I attached the code below.

Here is the code:

// index.js
import useGTM from 'https://cdn.skypack.dev/@elgorditosalsero/react-gtm-hook@2.0.2';
import React, { useEffect } from 'https://cdn.skypack.dev/react@17.0.1';
import ReactDOM from 'https://cdn.skypack.dev/react-dom@17.0.1';

const App = () => {
  const { init, UseGTMHookProvider } = useGTM()
  const gtmParams = {
    id: 'GTM-ID',
    dataLayerName: 'customDataLayerName'
  }

  useEffect(() => init(gtmParams), [])

  return (
    <UseGTMHookProvider>
      <MyComponent />
    </UseGTMHookProvider>
  )
}

const MyComponent = () => {

  useEffect(() => {
    alert('I was mounted');
    console.log('I was mounted');

    return () => {
      console.log('I was unmounted');
    }
  }, [])

  return (<div>
    My awesome app;
  </div>);
}

const ROOT_MOUNT = document.querySelector('#app');
ReactDOM.render(<App />, ROOT_MOUNT)
<!-- index.html -->
<div id="app"></div>

Here is the same code but in the code pen: https://codepen.io/vduding/pen/JjRjmLy

Thank you!

elgorditosalsero commented 3 years ago

Hey @vduding, I've done some tests and, right now, this is fine.

I mean, if you try to comment the init(gtmParams) inside your useEffect, you'll notice that's there are no more re-renders.

But, why this is happening?

It's happening because when you call for the first time the useGTM hook, it has an initialState with default values; when you call the init function, it will update the internal state of the hook and the context, so, it's that update of the context that triggers the renders.

It's how React's context works 😄

Hope this helps and any tips or PR are welcomed to improve the hook 🎉

elgorditosalsero commented 3 years ago

Closing due to inactivity.

@vduding feel free to re-open it if you have more questions.

djfarly commented 3 years ago

Hey @elgorditosalsero,

I've been playing around with this lib and this issue came to my attention as well. The problem is most noticeable when doing page-transition animations or having css-in-js styles as children.

The problem is that these lines:

  const UseGTMHookProvider = ({ children }: GTMHookProviderProps) => (
    <useGTMHookContext.Provider value={dataLayerState}>{children}</useGTMHookContext.Provider>
  )

create a completely new function on every parent-render. This new function means that React unmounts ALL children of UseGTMHookProvider and then creates new instances. This is very similar to giving components new keys to force a re-mount.