From 1f8eba9be3cc406d3449362004eca5b09cc5fc00 Mon Sep 17 00:00:00 2001 From: Joseph Chamochumbi Date: Mon, 8 Jul 2019 12:40:47 +0200 Subject: [PATCH] fix: solves unwanted unmount of wrapped components When withResponsiveProps invokes React.createElement, the first is withTheme(ResponsiveProps), this is always a new component. React cleans up this component when props change and mounts it again. Visually it is hard to notice the error, as very few DOM changes happen,depending on the setting. However this is noticeable with components running effects or life cycles. --- src/index.jsx | 18 ++++++------ src/index.spec.jsx | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/index.jsx b/src/index.jsx index 2df12cf..8febf6f 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -213,15 +213,17 @@ export class ResponsiveProps extends Component { /** HOC props proxy for dealing with responsive props. */ /* Enhances a styled component with the props responsiveProps */ +const ThemedResponsiveProps = withTheme(ResponsiveProps); + const withResponsiveProps = (WrappedComponent, mixins = {}) => { - return React.forwardRef((props, ref) => - React.createElement(withTheme(ResponsiveProps), { - forwardRef: ref, - wrappedComponent: WrappedComponent, - mixins, - ...props - }) - ); + return React.forwardRef((props, ref) => ( + + )); }; withResponsiveProps.displayName = "responsiveProps"; diff --git a/src/index.spec.jsx b/src/index.spec.jsx index bdcd2a2..3593ffe 100644 --- a/src/index.spec.jsx +++ b/src/index.spec.jsx @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import { act } from "react-dom/test-utils"; import { mount, shallow } from "enzyme"; import styled, { css, withTheme, ThemeProvider } from "styled-components"; import renderer from "react-test-renderer"; @@ -298,6 +299,75 @@ describe("withResponsiveProps", () => { }); }); + describe.only("Component wrapped withResponsiveProps mounts only once", () => { + // This test uses fake timers + jest.useFakeTimers(); + /* eslint-disable-next-line */ + const Counter = ({ responsiveProps }) => { + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + const timer = setTimeout(() => setCount(1), 4); + return () => clearTimeout(timer); + }); + + return ( + {count} + ); + }; + + const testMethod = jest.fn(); + + const WrappedCounter = withResponsivePropsHoc(Counter, { + testMethod + }); + + // hook to force a render of this component + const useForceUpdate = () => React.useReducer(state => !state, false)[1]; + /* eslint-disable-next-line */ + const Parent = () => { + const forceUpdate = useForceUpdate(); + return ( + <> + + + + ); + }; + + const wrapper = mount( + + + + ); + + it("Parent has a toggle button", () => { + expect(wrapper.find("button").text()).toEqual("toggle"); + }); + + it("Counter mounts with count 0", () => { + expect(wrapper.find("div").text()).toEqual("0"); + }); + + it("Counter updates count to 1 when timer is triggered", () => { + act(() => { + jest.runAllTimers(); + }); + wrapper.update(); + expect(wrapper.find("div").text()).toEqual("1"); + }); + + it("Counter keeps the count at one when clicking the toggle button", () => { + act(() => { + wrapper.find("button").simulate("click"); + }); + wrapper.update(); + expect(wrapper.find("div").text()).toEqual("1"); + }); + }); + describe("forwardRef", () => { const WrappedComponent = withResponsivePropsHoc(TestWrapped, {}); const forwardRef = React.createRef(null);