Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setState getting into infinite loop #1784

Closed
artola opened this issue Aug 21, 2018 · 8 comments
Closed

setState getting into infinite loop #1784

artola opened this issue Aug 21, 2018 · 8 comments

Comments

@artola
Copy link

artola commented Aug 21, 2018

A component that fires setState at componentDidMount and componentDidUpdate enters in an infinite loop. Here in a simplified example that produces this situation.
Notice:

  • with versions 3.4.0 and 3.4.1 is OK.
  • since 3.4.2 it breaks.
  • commenting out 1 of the lifecycle calls, it pass.
  • same code with mount also breaks.

Interesting:

  • wrapping the 1 of the setState with asetTimeout WORKS.
componentDidUpdate() {
  setTimeout(() => {
    this.setState({value: false});
  });
}

Example test:

import React, {Component} from 'react';
import {shallow} from 'enzyme';

class Example extends Component {
  constructor(props: any) {
    super(props);

    this.state = {
      value: false,
    };
  }

  componentDidMount() {
    this.setState({value: false});
  }

  componentDidUpdate() {
    this.setState({value: false});
  }

  render() {
    return React.createElement('div', null, this.state.value);
  }
}

it('should not go crazy', () => {
  expect(() => {
    shallow(React.createElement(Example));
  }).not.toThrow();
});

With "enzyme": "3.4.0" and "enzyme": "3.4.1":

 PASS  src/components/example.test.ts

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

With "enzyme": "3.4.2":

 FAIL  src/components/example.test.ts
  ● should not go crazy

    expect(function).not.toThrow()

    Expected the function not to throw an error.
    Instead, it threw:
      Method “setState” is only meant to be run on a single node. undefined found instead.
          24 | }
          25 |
        > 26 | it('should not go crazy', () => {
             |          ^
          27 |   expect(() => {
          28 |     shallow(React.createElement(Example));
          29 |   }).not.toThrow();

          at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1718:17)
          at ShallowWrapper.setState (node_modules/enzyme/build/ShallowWrapper.js:499:14)
          at Example.ShallowWrapper.instance.setState (node_modules/enzyme/build/ShallowWrapper.js:184:33)
          at Example.componentDidMount (src/components/example.test.ts:26:10)
          at node_modules/enzyme/build/ShallowWrapper.js:189:20
          at Object.batchedUpdates (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:392:22)
          at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:188:24)
          at shallow (node_modules/enzyme/build/shallow.js:21:10)
          at src/components/example.test.ts:46:25
          at Object.<anonymous> (node_modules/expect/build/to_throw_matchers.js:51:9)
          at Object.throwingMatcher [as toThrow] (node_modules/expect/build/index.js:320:33)
          at Object.<anonymous> (src/components/example.test.ts:47:10)

With "enzyme": "3.4.3" and "enzyme": "3.4.4":

 FAIL  src/components/example.test.ts
  ● should not go crazy

    expect(function).not.toThrow()

    Expected the function not to throw an error.
    Instead, it threw:
      RangeError: Maximum call stack size exceeded
          30 | });
          31 |

          at withSetStateAllowed (node_modules/enzyme/build/Utils.js:296:21)
          at ShallowWrapper.<anonymous> (node_modules/enzyme/build/ShallowWrapper.js:511:42)
          at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1735:25)
          at ShallowWrapper.setState (node_modules/enzyme/build/ShallowWrapper.js:510:14)
          at Example.ShallowWrapper.instance.setState (node_modules/enzyme/build/ShallowWrapper.js:198:33)
          at Example.componentDidUpdate (src/components/example.test.ts:32:10)
          at node_modules/enzyme/build/ShallowWrapper.js:548:28
          at withSetStateAllowed (node_modules/enzyme/build/Utils.js:300:3)
          at ShallowWrapper.<anonymous> (node_modules/enzyme/build/ShallowWrapper.js:511:42)
          at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1735:25)
          at ShallowWrapper.setState (node_modules/enzyme/build/ShallowWrapper.js:510:14)
          at Example.ShallowWrapper.instance.setState (

---
REPEATED REPEATED REPEATED 
---
node_modules/enzyme/build/ShallowWrapper.js:198:33)
          at Example.componentDidUpdate (src/components/example.test.ts:32:10)
          at node_modules/enzyme/build/ShallowWrapper.js:548:28
          at withSetStateAllowed (node_modules/enzyme/build/Utils.js:300:3)
          at ShallowWrapper.<anonymous> (node_modules/enzyme/build/ShallowWrapper.js:511:42)
          at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1735:25)
          at ShallowWrapper.setState (node_modules/enzyme/build/ShallowWrapper.js:510:14)
          at Example.ShallowWrapper.instance.setState (node_modules/enzyme/build/ShallowWrapper.js:198:33)
          at Example.componentDidUpdate (src/components/example.test.ts:32:10)

Expected behavior
No loop.

Additional context

"jest": "^23.5.0"
"react": "^16.4.2"
"enzyme": "3.4.4"
"enzyme-adapter-react-16": "^1.2.0"

@ljharb
Copy link
Member

ljharb commented Aug 21, 2018

I think this is a duplicate of #1783.

@artola
Copy link
Author

artola commented Aug 21, 2018

@ljharb It could be that #1783 and #1784 are related. I just added some workarounds too, might be helpful to find the culprit.

@ljharb
Copy link
Member

ljharb commented Aug 21, 2018

cc @koba04 on this one too

@Ailrun
Copy link

Ailrun commented Aug 21, 2018

And actually, your case makes infinite loop even in react. Could you check it in the react sandbox?

@artola
Copy link
Author

artola commented Aug 22, 2018

@Ailrun yep, infinite loop happening in React, when:

  • "Component", it works by commenting 1 method, or wrapping the call in setTimeout.

Fix for React: https://codesandbox.io/s/0mr5y064l

  • "PureComponent", it just works as no change is detected (shouldComponentUpdate).

Nevetheless, after changing the type of component to PureComponent it continues to break in test with Enzyme. Might be that Enzyme if firing the event without checking shouldComponentUpdate?
The component in our codebase is a PureComponent, tested thousand of times in last 2 years.

What is "strange", is that I discover this by debugging why a test that has being working for a life, started to fail.

@Ailrun
Copy link

Ailrun commented Aug 22, 2018

IMO, then, your issue is about checking shouldComponentUpdate, not about setState, componentDidMount and componentDidUpdate combinations.

@koba04
Copy link
Contributor

koba04 commented Aug 22, 2018

@artola
After enzyme v3.4.2, componentDidUpdate is always called if options.disableLifecycleMethods isn't enabled.
In this case, the followling didn't invoke componentDidUpdate before v3.4.2 so the update doesn't cause the infinite loop.

  componentDidMount() {
    this.setState({value: false});
  }

  componentDidUpdate() {
    this.setState({value: false});
  }

But this code would cause an inifinite loop with react-dom so I think this is not a bug for enzyme.

"PureComponent", it just works as no change is detected (shouldComponentUpdate).

I found an issue that enzyme doesn't handle PureComponent well. I'll fix it.
Thanks!

@koba04
Copy link
Contributor

koba04 commented Aug 22, 2018

I found an issue that enzyme doesn't handle PureComponent well. I'll fix it.

The PR is #1786

ljharb pushed a commit to koba04/enzyme that referenced this issue Aug 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants