diff --git a/packages/material-ui/src/ButtonBase/TouchRipple.js b/packages/material-ui/src/ButtonBase/TouchRipple.js
index a8054300a3d1df..e6d512b78d81bc 100644
--- a/packages/material-ui/src/ButtonBase/TouchRipple.js
+++ b/packages/material-ui/src/ButtonBase/TouchRipple.js
@@ -207,17 +207,22 @@ const TouchRipple = React.forwardRef(function TouchRipple(props, ref) {
// Touche devices
if (event.touches) {
- // Prepare the ripple effect.
- startTimerCommit.current = () => {
- startCommit({ pulsate, rippleX, rippleY, rippleSize, cb });
- };
- // Delay the execution of the ripple effect.
- startTimer.current = setTimeout(() => {
- if (startTimerCommit.current) {
- startTimerCommit.current();
- startTimerCommit.current = null;
- }
- }, DELAY_RIPPLE); // We have to make a tradeoff with this value.
+ // check that this isn't another touchstart due to multitouch
+ // otherwise we will only clear a single timer when unmounting while two
+ // are running
+ if (startTimerCommit.current === null) {
+ // Prepare the ripple effect.
+ startTimerCommit.current = () => {
+ startCommit({ pulsate, rippleX, rippleY, rippleSize, cb });
+ };
+ // Delay the execution of the ripple effect.
+ startTimer.current = setTimeout(() => {
+ if (startTimerCommit.current) {
+ startTimerCommit.current();
+ startTimerCommit.current = null;
+ }
+ }, DELAY_RIPPLE); // We have to make a tradeoff with this value.
+ }
} else {
startCommit({ pulsate, rippleX, rippleY, rippleSize, cb });
}
diff --git a/packages/material-ui/src/ButtonBase/TouchRipple.test.js b/packages/material-ui/src/ButtonBase/TouchRipple.test.js
index 64b9ada9ce3662..9b66633f70f0e2 100644
--- a/packages/material-ui/src/ButtonBase/TouchRipple.test.js
+++ b/packages/material-ui/src/ButtonBase/TouchRipple.test.js
@@ -18,7 +18,7 @@ describe('', () => {
*/
function renderTouchRipple(other) {
const touchRippleRef = React.createRef();
- const { container } = render(
+ const { container, unmount } = render(
', () => {
queryRipple() {
return container.querySelector('.ripple');
},
+ unmount,
};
}
@@ -155,6 +156,9 @@ describe('', () => {
});
describe('mobile', () => {
+ /**
+ * @type {ReturnType}
+ */
let clock;
before(() => {
@@ -227,5 +231,17 @@ describe('', () => {
expect(queryAllActiveRipples()).to.have.lengthOf(0);
expect(queryAllStoppingRipples()).to.have.lengthOf(0);
});
+
+ it('should not leak on multi-touch', function multiTouchTest() {
+ const { instance, unmount } = renderTouchRipple();
+
+ instance.start({ type: 'touchstart', touches: [{}] }, () => {});
+ instance.start({ type: 'touchstart', touches: [{}] }, () => {});
+ unmount();
+
+ // expect this to run gracefully without
+ // "react state update on an unmounted component"
+ clock.runAll();
+ });
});
});