React组件多次挂载,导致在useEffect中出现问题。

huangapple go评论54阅读模式
英文:

React component mounts multiple times and causes problems in useEffect

问题

我们在我们的React应用程序中向Google Analytics发送一些数据。我们将它们发送到我们的自定义钩子中。为了防止发送重复的值,我们引入了一个“标记”字符串,它将获得正在发送的任何内容的值。每次调用该钩子时,它都会创建一个新的“标记”,将其与旧的标记进行比较,如果它们相同,则不应发送任何内容到GA。否则,它将被发送,并且标记将被更新。至少这应该是它的工作方式。出于某种原因,该钩子会触发四次(这仍然可以接受)。但是,由于某种原因,状态没有及时更新,等号运算符无法判断标记字符串不应该相等。因此,相同的数据会发送到GA两次。我需要理解为什么会发生这种情况。

注意: 我编辑了问题和代码,以反映*@Keith@ramkumar2098**帮助我找出的结果。*

通过 strategically placed console.log,我注意到效果被调用了4次。在第一和第二次迭代中,trackedMarker为空,newMarker被正确设置,但看起来setTrackedMarker(newMarker)不会及时产生任何结果(我知道它是异步的,但仍然...)。在第三和第四次迭代中,trackedMarkernewMarker都具有相同的值(这是预期的正确状态,数据发送到GA后)。在代码注释中有更多细节:

钩子:

export const useGA = (
  someKey: string,
  results: readonly Readonly<OurResultType>[],
): void => {
  const [trackedMarker, setTrackedMarker] = useState('');
  const gtm = useGoogleTagManager();

  useEffect(() => {
    console.log('Effect called!'); // 调用4次,对于1次页面加载
    const newMarker = someKey + results.map((r) => r.id).join(',');

    console.log('Tracked marker: ' + trackedMarker); // 在第一和第二次日志迭代中为空
    console.log('New marker:     ' + newMarker); // 每次都有正确的值

    if (trackedMarker !== newMarker) {
      setTrackedMarker(newMarker);
      gtm.push(...);
      console.log('Pushed!');
    }
    console.log('New tracked marker: ', trackedMarker); // 前两次为空,第三和第四次有正确的值
  }, [someKey, results, trackedMarker, gtm]);
};

使用该钩子的组件:

export const OurComponent: React.FC<OurComponentProps> = ({
  someKey,
}) => {
  ...
  useGA(someKey, results);
  ...
}

使用OurComponent的组件:

export const SomeUpperComponent: React.FC<SomeUpperComponentProps> = ({
  hasResults,
  someKey,
}) => {
  ...
  return hasResults ? <OurComponent someKey={someKey} /> : <NoResults />;
};

这些来自钩子的console.log输出如下:

第一次:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker:
Pushed!

第二次:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker:
Pushed!

第三次:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10

第四次:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10

所以显然效果被多次调用,这是核心问题。在第二次调用时,标记仍然不同,推送到GA多次。最初我以为JavaScript表现怪异,不能判断两个字符串是否相等,但现在看起来是组件多次挂载的一些问题,状态没有及时更新...

英文:

We are sending some data to Google Analytics in our React application. We are sending them in our custom hook. To prevent sending duplicate values, we introduced a "marker" string which will get the value of whatever is being sent. Every time the hook is called, it will create a new "marker", compare it to the old one and if they are the same, nothing should be sent to GA. Otherwise, it will be sent and the marker will be updated. At least that's how this should work. For some reason the hook fires four times (which is still fine). However, for some other reason the state is not being updated on time, and the equality operator is not able to figure out that the marker strings shouldn't be equal. As the consequence, the same data gets sent to the GA twice. I need to understand why this is happening.

Note: I edited the question and the code to reflect the findings which @Keith and @ramkumar2098 helped me figure out.

With strategically placed console.logs, I noticed that the effect gets called 4 times. In the first and second iteration, trackedMarker is empty and newMarker is set correctly, but it looks like setTrackedMarker(newMarker) doesn't produce any result on time (I know it's async, but still...). In the third and fourth iteration, both trackedMarker and newMarker have identical values (which is the correct state to expect after the data is sent to the GA). More details below in the code comments:

Hook:

export const useGA = (
  someKey: string,
  results: readonly Readonly&lt;OurResultType&gt;[],
): void =&gt; {
  const [trackedMarker, setTrackedMarker] = useState(&#39;&#39;);
  const gtm = useGoogleTagManager();

  useEffect(() =&gt; {
    console.log(&#39;Effect called!&#39;); // called 4 times for 1 page load
    const newMarker = someKey + results.map((r) =&gt; r.id).join(&#39;,&#39;);

    console.log(&#39;Tracked marker: &#39; + trackedMarker); // Empty in the first and second log iteration
    console.log(&#39;New marker:     &#39; + newMarker); // Has correct value every time

    if (trackedMarker !== newMarker) {
      setTrackedMarker(newMarker);
      gtm.push(...);
      console.log(&#39;Pushed!&#39;);
    }
    console.log(&#39;New tracked marker: &#39;, trackedMarker); // First two times empty, on the third and fourth time has correct value
  }, [someKey, results, trackedMarker, gtm]);
};

Component which is using the hook:

export const OurComponent: React.FC&lt;OurComponentProps&gt; = ({
  someKey,
}) =&gt; {
  ...
  useGA(someKey, results);
  ...
}

The component which is using OurComponent:

export const SomeUpperComponent: React.FC&lt;SomeUpperComponentProps&gt; = ({
  hasResults,
  someKey,
}) =&gt; {
  ...
  return hasResults ? &lt;OurComponent someKey={someKey} /&gt; : &lt;NoResults /&gt;;
};

Those console.logs from the hook output the following:

1st time:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker:
Pushed!

2nd time:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker:
Pushed!

3rd time:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10

4th time:

Effect called!
Tracked marker:
New marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10
New tracked marker: 9018a12a-6cd1-4020-91ca-a05a3a81f1744,2,5,3,1,8,7,6,9,10

So apparently the effect gets called multiple times and that's the core issue. On the second call, the markers are still different, and the push to GA is fired one more time. Initially I thought that JavaScript is acting weird and not able to figure out that two strings are equal but now it looks like that there is some issue with component mounting multiple times for unknown reason and the state not being updated on time...

Credits:

As ramkumar2098 pointed out in the answer, I had an issue with console.logs, because I was using "+" instead of "," and it affected the result of the comparison in the log itself.

As Keith pointed out in the comments, I should've made sure how many times the component is being mounted and that resulted in the updated question and more detailed description of the issue.

答案1

得分: 2

Use comma instead of + operator in your console logs.

console.log('Condition 1:', marker !== newMarker);
console.log('Condition 2:', marker != newMarker);

The + operator concatenates 'Condition 1:' and marker and compares that with newMarker.

英文:

Use comma instead of + operator in you console logs.

console.log(&#39;Condition 1:&#39;, marker !== newMarker);
console.log(&#39;Condition 2:&#39;, marker != newMarker);

The + operator concatenates 'Condition 1: ' and marker and comparing that with newMarker.

huangapple
  • 本文由 发表于 2023年6月8日 14:03:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76429005.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定