You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React/Next页面(重新)加载触发GTM事件,避免组件重渲染触发

React/Next应用中GTM事件触发逻辑问题

需求场景

我开发了一个React/Next应用,需要在搜索结果页面(重新)加载时向Google Tag Manager(GTM)发送事件,触发场景包括:

  • 页面初始加载时
  • 用户进入结果详情页后返回时
  • 用户刷新页面且显示相同结果时

核心限制:当页面组件因其他原因(inquiryKey和结果未变,比如自身或父组件事件导致)重渲染时,不能向GTM发送事件。

现有代码实现

父页面组件(ResultsContent)

export const ResultsContent: React.FC<ResultsContentProps> = ({
  hasResults,
  inquiryKey,
}) => {  
  return hasResults ? <ResultsComponent inquiryKey={inquiryKey} /> : <NoResults />;
};

结果列表组件(ResultsComponent)

export const ResultsComponent: React.FC<ResultsComponentProps> = ({
  inquiryKey,
}) => {
  // 从Redux获取结果,结果由上一页的表单输入计算生成
  const results = useSelector(getResults);
  const gtm = useGoogleTagManager();

  /* 问题代码段 */
  useEffect(() => {
    gtm.push(createEvent(results)); // 生成符合要求格式的事件对象
  }, []); // 空依赖数组,期望避免多次执行

  return (/* 组件渲染内容 */);
}

尝试过的优化方案

我试过多种useEffect依赖数组配置:

  • 空数组[]
  • [results]
  • [...results]
  • [inquiryKey, results]
  • [inquiryKey, ...results]

以上配置都没能解决问题;还尝试用useCallback替代useEffect,结果导致更多测试用例失败。

测试用例情况

失败的测试用例

it('should contain only one event when rerendered', () => {
    const inquiryKey = 'abc123';
    const { rerender } = render(<ResultsComponent inquiryKey={inquiryKey} />);
    rerender(<ResultsComponent inquiryKey={inquiryKey} />);
    expect(
      window.dataLayer.filter((el) => el.event === `event_name_goes_here`).length,
    ).toBe(1); // 测试失败,实际检测到2个事件
  });

通过的测试用例

it('should contain only one event when rendered only once', () => {
    render(<ResultsComponent inquiryKey={inquiryKey} />);
    expect(
      window.dataLayer.filter((el) => el.event === `event_name_goes_here`).length,
    ).toBe(1);
  });

it('should contain three events when rendered three times', () => {
    render(<ResultsComponent inquiryKey={inquiryKey} />);
    render(<ResultsComponent inquiryKey={inquiryKey} />);
    render(<ResultsComponent inquiryKey={inquiryKey} />);
    expect(
      window.dataLayer.filter((el) => el.event === `event_name_goes_here`).length,
    ).toBe(3);
  });

疑问

不确定这个失败的测试用例是否编写正确:rerender方法是否真的会重渲染组件?它和再次调用render有什么区别?

调试补充信息

按照建议,我在ResultsComponent中添加了如下调试代码:

const counter = useRef<number>(0);
console.log('*** COUNTER ***', counter.current);
counter.current++;

单独运行失败的测试用例时,输出如下:

console.log
*** COUNTER *** 0
at log (src/components/ResultsComponent/ResultsComponent.tsx:63:11)
console.log
*** COUNTER *** 1
at log (src/components/ResultsComponent/ResultsComponent.tsx:63:11)
console.log
*** COUNTER *** 2
at log (src/components/ResultsComponent/ResultsComponent.tsx:63:11)

Warning: Cannot update a component (`SomeBannerComponent`) while rendering a different component (`TestProviders`). To locate the bad setState() call inside `TestProviders`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
        at store (/some_path/TestProviders.tsx:67:3)
        at wrapper

      25 |   return render(<ResultsComponent inquiryKey={inquiryKey} />, {
      26 |     init(store) {
    > 27 |       store.dispatch(
         |             ^
      28 |         doSaveInquiryInputs(
      29 |           inquiryKey,
      30 |           {

...(完整栈追踪内容省略)...
 at rerender (node_modules/@testing-library/react/dist/pure.js:178:7)
      at Object.<anonymous> (src/components/ResultsComponent/ResultsComponent.test.tsx:249:5)

(第249行是调用rerender方法的代码行)

之后又输出两条日志:

console.log
*** COUNTER *** 0
at log (src/components/ResultsComponent/ResultsComponent.tsx:63:11)
console.log
*** COUNTER *** 1
at log (src/components/ResultsComponent/ResultsComponent.tsx:63:11)
console.log

内容的提问来源于stack exchange,提问作者vtomic85

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 11:27:51