From 0a1e06ae275fb3878e4d909538a73d640b6d2220 Mon Sep 17 00:00:00 2001 From: Corentin Ardeois Date: Mon, 7 Mar 2022 12:00:47 -0500 Subject: [PATCH] fix: state leak when component is unmounted When the component was unmounted before Google Optimize was loaded, we had a state update that was causing an exception on dev mode Check if the component is unmounted before updating the state fixes the issue --- src/Experiment.js | 6 +++++- test/specs/experiment.spec.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Experiment.js b/src/Experiment.js index 0da7c5cc..2130ec51 100644 --- a/src/Experiment.js +++ b/src/Experiment.js @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import OptimizeContext from "./OptimizeContext"; class Experiment extends React.Component { + isUnmounted = false; state = { variant: null, }; @@ -48,7 +49,9 @@ class Experiment extends React.Component { ); const oldHideEnd = window.dataLayer.hide.end; window.dataLayer.hide.end = () => { - this.updateVariantFromGlobalState(); + if (!this.isUnmounted) { + this.updateVariantFromGlobalState(); + } oldHideEnd && oldHideEnd(); }; @@ -82,6 +85,7 @@ class Experiment extends React.Component { componentWillUnmount() { clearTimeout(this.updateVariantTimeout); + this.isUnmounted = true; typeof window !== "undefined" && window.gtag && window.gtag("event", "optimize.callback", { diff --git a/test/specs/experiment.spec.js b/test/specs/experiment.spec.js index 8798b266..52ec0bc5 100644 --- a/test/specs/experiment.spec.js +++ b/test/specs/experiment.spec.js @@ -4,6 +4,11 @@ import sinon from "sinon"; import { Experiment } from "../../src"; describe("experiment", () => { + afterEach(() => { + delete window.google_optimize; + delete window.dataLayer; + }); + it("should require experiment id", () => { expect(() => shallow()).to.throw(); }); @@ -50,5 +55,32 @@ describe("experiment", () => { expect(wrapper.find(Loader)).to.have.lengthOf(1); }); + + it("should update variant after optimize is loaded", () => { + delete window.dataLayer; + const wrapper = shallow(); + + expect(wrapper.state("variant")).to.be.equal(null); + + // Load google optimize + window.google_optimize = { get: sinon.stub().returns("2") }; + window.dataLayer.hide.end(); + + expect(wrapper.state("variant")).to.be.equal("2"); + }); + + it("should not update variant after optimize is loaded if component was unmounted", () => { + delete window.dataLayer; + const wrapper = shallow(); + + expect(wrapper.state("variant")).to.be.equal(null); + + wrapper.unmount(); + // Load google optimize + window.google_optimize = { get: sinon.stub().returns("2") }; + + // If component state is updated while component is unmounted, this call will crash + window.dataLayer.hide.end(); + }); }); });