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

change the argument passed to CallbackQueue.getPooled #10101

Merged
merged 10 commits into from
Jul 12, 2017

Conversation

gwmccull
Copy link
Contributor

@gwmccull gwmccull commented Jul 4, 2017

fix to #10100

@gaearon
Copy link
Collaborator

gaearon commented Jul 4, 2017

This code has not changed in a very long time, and doesn't seem to have changed between 15 and 16.

Could you please:

  1. Add a unit test for the regression
  2. Explain why this wasn't a problem in 15 despite the same code at this line

Thanks!

@gwmccull
Copy link
Contributor Author

gwmccull commented Jul 4, 2017

@gaearon ok, I think I mostly have it worked out. The code change that actually exposed this bug was in:

/src/renderers/shared/stack/reconciler/ReactUpdates.js

In version 15.4.1, you see this code:

var UPDATE_QUEUEING = {
  initialize: function() {
    this.callbackQueue.reset();
  },
  close: function() {
    this.callbackQueue.notifyAll();
  },
};

function ReactUpdatesFlushTransaction() {
  /* redacted */
  this.callbackQueue = CallbackQueue.getPooled();
  /* redacted */
}

The setState callbacks are getting pushed into that callbackQueue and run in that context. I can prove it by adding changing it to CallbackQueue.getPooled(1) and seeing that my callback function is run with a parameter of 1.

Since getPooled has no arguments, the parameter passed to the callback function will always be undefined.

In version 16, all of that code is gone and the CallbackQueue is apparently being taken from the component or another intermediary instead. That's why when I made the change in my PR, it changed the callback function from being called with null to being called with undefined.

I've created a unit test for ReactNativeReconcileTransaction but this is kind of a hard issue to test since testing the bug I actually experienced would involve unit tests on the React Native side of things.

@gaearon
Copy link
Collaborator

gaearon commented Jul 4, 2017

I've created a unit test for ReactNativeReconcileTransaction but this is kind of a hard issue to test since testing the bug I actually experienced would involve unit tests on the React Native side of things.

Can you rewrite the test to verify observable API instead of internals? In this particular case, you could write a test that schedules a setState callback, and then assert that it does’t get extra arguments.

This is important because the "transaction" code will be removed soon anyway. We want to ensure we doesn't regress on this behavior even if it's implemented in some different way.

To force it to follow RN code path, you can do something like this.

@gwmccull
Copy link
Contributor Author

gwmccull commented Jul 5, 2017

I replaced the test with one that tests setState directly with a mounted component.

the format of the test is a bit odd. Originally, I had:

  it('calls setState with the correct parameters', () => {
    var mockCallback = jest.fn();
    class Component extends React.Component {
      componentDidMount() {
        this.setState({}, mockCallback);
      }
      render() {
        return false;
      }
    }

    ReactNative.render(<Component />, 11);
    expect(mockCallback).toBeCalledWith(undefined);
  });

That test worked as expected when I ran jest on it directly. But when I ran the scripts/fiber/record-tests it failed. Somehow the mock function was being called with [undefined] when I ran the test directly but [] when I ran the test with the script.

I'm not sure what is causing the difference but this gets at the arguments with a closure rather than the jest mock functions so it should be a valid test

@gaearon
Copy link
Collaborator

gaearon commented Jul 5, 2017

Can you please run yarn prettier to prettify the code and commit the result?
Thanks!

@@ -108,4 +108,20 @@ describe('ReactNative', () => {
ReactNative.render(<Component chars={after} />, 11);
expect(UIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
});

it('calls setState with the correct parameters', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a similar test to ReactTestUtils-test for shallow renderer, and to ReactCompositeComponent-test for DOM renderer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@gwmccull
Copy link
Contributor Author

gwmccull commented Jul 5, 2017

@gaearon I created those additional tests but to get them to pass, I had to fix another instance of that bug. It looks like that code is related to React for the web.

I don't use React on the web so I can't easily verify if this was an issue

@gwmccull
Copy link
Contributor Author

gwmccull commented Jul 5, 2017

I finally got prettier to run.

npm run prettier didn't seem to do anything. Had to run npm run prettier-all

@gwmccull
Copy link
Contributor Author

@gaearon Do I need to do anything else on this PR? Just want to make sure this isn't waiting on me to do something

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small changes.

@@ -844,4 +844,20 @@ describe('ReactTestUtils', () => {
);
});
});

it('should call setState with the correct parameters', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's change this to should call setState callback with no arguments (and others too)


it('should call setState with the correct parameters', () => {
let mockArg = 'the wrong value';
let mockCallback = arg => (mockArg = arg);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe let's assert arguments.length is 0 instead of checking first argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I tried this but npm test causes the length of arguments to be 1, [ undefined ]. But node ./scripts/fiber/record-tests has the length of the arguments as 0, [ ]

So it seems like checking the first argument is the only way to go that I see

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you see where that undefined comes from? I’d like to understand it.

@gaearon
Copy link
Collaborator

gaearon commented Jul 11, 2017

Thanks for your work. This mostly looks good, just a few final comments.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra argument is coming from here. Seems like CallbackQueue assumes argument always exists. (cc @aweary, we introduced this arity issue in #7649)

But I don’t see us using this argument anymore. If you search for getPooled() calls it’s always null. The code that relied on it was deleted.

So let’s just completely remove _arg from CallbackQueue, stop passing it, and remove the argument to getPooled() as well. Then you can fix up the tests to check arguments.length too.

@gwmccull
Copy link
Contributor Author

@gaearon ok, I removed arg and cleaned up the Flow types for CallbackQueue. I also fixed up the tests

@gaearon gaearon merged commit 50d905b into facebook:master Jul 12, 2017
@gaearon
Copy link
Collaborator

gaearon commented Jul 12, 2017

This looks awesome. Thank you! This will get into React Native with the next sync. Note that this code doesn’t live in react package so you’ll need to wait for react-native release including new code. I don’t know their schedule for sure so it’s a bit hard for me to say if the sync will make it into next release or not. Anyway, congrats on the contribution!

@gwmccull
Copy link
Contributor Author

@gaearon thanks for all your feedback!

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.

3 participants