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

using the wrong renderer's act() should warn #15398

Conversation

threepointone
Copy link
Contributor

@threepointone threepointone commented Apr 12, 2019

closed this in favour of #15399


via #15319
This solves 2 specific problems -

  • using the 'wrong' act() shouldn't silence the warning:
    We do this by using a new empty object on the reconciler (called ReactActingUpdatesSigil, but I should rename that). act() sets this object before the callback, and unsets it when it's over. Then warnIfNotCurrentActingUpdates() not only checks whether ReactShouldWarnActingUpdates.current is set, but also whether it matches the expected sigil.

  • using the wrong act() logs a warning
    Using the same above method, we can check whether you're using the right version of act() for your code.

Now, I first added this check only for state hook updates, but it didn't reliably catch the common failure case. Consider the following component -

      function App() {
        let [state, setState] = React.useState(0);
        async function ticker() {
          await null;
          setState(x => x + 1);
        }
        React.useEffect(() => {
          ticker();
        }, [Math.min(state, 4)]);
        return state;
      }

Let's write a test for it using the shiny new async act()

        const el = document.createElement("div");
        await ReactTestUtils.act(async () => {
          ReactDOM.render(React.createElement(App), el);
        });
        // all 5 ticks present and accounted for
        console.log(el.innerHTML); // 5!

This is the golden path - use the correct act with the matching renderer, and you'll get expected behaviour. Here's a quick diagram of the timeline it goes through
image
Of note, because we can check everytime after calling flushPassiveEffects(), we can guarantee that we the act() 'scope' will stay open until the effects queue is drained. GOod.

Now, let's use a mismatching act()-

       await ReactTestUtils.act(async () => {
          ReactTestRenderer.create(React.createElement(App));
        });

Now, let's say we'd added our sigil check only for updates, you'd think it would still trigger the warning. However, the timing of things has changed. There are 2 scenarios of how these will be sequenced out. The first, which is the 'good' version -

image

Because we can't use flushPassiveEffects() as expected (ie - it's just a no op for other renderer instances), we have to rely on the browser/jest environment to flush to the 'screen', and then the effects/updates fire. In this 'good' case, at least one set state call happens inside the act scope, so we can do the sigil check and warn that they aren't using the right act version.

However, the bad news is that this happens super rarely (in my rough estimation, only 1 in 20 'successes'). In reality, it usually happens outside the scope of the act scope.

image

Here, you'll see that the effects fire after the act scope has closed, so when we do the sigil check, we can only warn that the dev hasn't wrapped their code with act() (which will confuse them, since they think they already have)

So how do we fix this? Well, I noticed that most of these failures happen at the very start, when they initialise (TestRenderer.create(), ReactDOM.render(), etc). I believe that if we also add just the sigil identity check in the reconciler's .createContainer(), we should be able to warn for most cases asap that they're using the wrong version (and ofc, createContainer is synchronous and won't escape the act() scope).

This PR does the above. I'm opening this PR as a draft just to make sure my approach is right. I haven't written jest tests yet, and was using my act-dom.html fixture for testing this. I'm adding jest tests asap. Maybe the problem I described above won't be as big problem in jest. Or maybe it will! It's an adventure to find out.

Also I don't like the variable name ReactActingUpdatesSigil. It's cool, but I'll save it for when I want to feel clever later.

Sunil Pai added 7 commits April 3, 2019 17:11
via facebook#15319
This solves 2 specific problems -
- using the 'wrong' act() doesn't silence the warning
- using the wrong act logs a warning

It does this by using an empty object on the reconciler as the identity of ReactShouldWarnActingUpdates.current. We also add this check when calling createContainer() to catch the common failure of this happening right in the beginning.
@sizebot
Copy link

sizebot commented Apr 12, 2019

Warnings
⚠️

Could not find build artifacts for base commit: 3a44cce

Generated by 🚫 dangerJS

@threepointone
Copy link
Contributor Author

closing this because of whatever this mess is, opening a new one asap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants