I have a React/Next application and I need to send an event to Google Tag Manager whenever a page with search results (re)loads:
But besides that, I have a requirement not to send an event to GTM in case that the component which is used in that page rerenders for any other reason (meaning if the inquiryKey and the results do not change, but it gets rerendered because of some event in it or in its parent or whatever else could be the reason).
With the code that I currently have, when I console.log(windows.dataLayer)
in the browser console everything looks fine, but the test in which I'm using rerender
is failing. Here's what I have so far:
Parent page:
export const ResultsContent: React.FC<ResultsContentProps> = ({
hasResults,
inquiryKey,
}) => {
return hasResults ? <ResultsComponent inquiryKey={inquiryKey} /> : <NoResults />;
};
Results Component:
export const ResultsComponent: React.FC<ResultsComponentProps> = ({
inquiryKey,
}) => {
// Get results from Redux, calculated based on the form inputs on the previous page
const results = useSelector(getResults);
const gtm = useGoogleTagManager();
/* THIS IS THE PROBLEMATIC PART */
useEffect(() => {
gtm.push(createEvent(results)); // makes an event object in a format that we need
}, []); // empty dependency array to prevent it from running multiple times
return (/* Component body */);
}
For the dependency array I tried: [], [results], [...results], [inquiryKey, results] and [inquiryKey, ...results]
, but no success. I also tried useCallback
instead of useEffect
, but that breaks even more tests...
Here's the test which is failing:
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); // It fails, since it found 2 elements
});
...and two more tests which are passing:
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);
});
I'm not sure if I wrote the failing test correctly... Does the rerender
method really rerenders a component? What's the difference between this and calling render
again?
Edit:
As @adsy proposed, I added a few lines to the ResultsComponent
:
const counter = useRef<number>(0);
console.log('*** COUNTER ***', counter.current);
counter.current++;
When I run only the problematic test, here's the output:
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 | {
... (full stack trace goes here) ...
at rerender (node_modules/@testing-library/react/dist/pure.js:178:7)
at Object.<anonymous> (src/components/ResultsComponent/ResultsComponent.test.tsx:249:5)
(line 249 is the line where I call the rerender
method)
...and then below that two logs again:
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
Mystery solved - there was something wrong with our wrapper, so it was rerendering components too many times for some reason.