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
相关产品推荐
相关产品推荐

