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

Schedule state and callback at the same time #8585

Merged
merged 2 commits into from
Dec 21, 2016

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Dec 15, 2016

Fixes an issue (in both Stack and Fiber) where enqueueSetState causes a synchronous update that flushes before enqueueCallback is ever called. Now enqueueSetState et al accept an optional callback so that both are scheduled at the same time.

@acdlite acdlite force-pushed the batchstateandcallback branch 2 times, most recently from d99319c to 9f68a27 Compare December 20, 2016 21:08
@acdlite acdlite changed the title [WIP] Schedule state and callback at the same time Schedule state and callback at the same time Dec 20, 2016
const isTopLevelUnmount = Boolean(
partialState &&
partialState.element === null
);
Copy link
Contributor

@bvaughn bvaughn Dec 20, 2016

Choose a reason for hiding this comment

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

At a glance, this change seems unrelated to the core of this PR, and also like the opposite of what the logic used to be. Was it a bug before? I don't see any other change inside of this PR that looks as though it would have invalidated the previous check, but maybe I'm missing it.

eg I would expect to see a change more like

const isTopLevelUnmount = (
  partialState === null ||
  partialState.element === null
);

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

partialState at the top level is always an object with the shape { element: something }. The previous test was just wrong. I thought about writing a unit test for this but it's unobservable except in incremental mode. And when we do switch to incremental by default the top-level mount/update/unmount API will have to change anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Your suggested change is wrong because

Object.assign({}, { element: element }, null);

merely copies the previous state, whereas an unmount sets element to null.

Copy link
Contributor

Choose a reason for hiding this comment

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

The previous test was just wrong.

Gotcha. This is what I thought may be the case but I wasn't sure.

} else {
internalInstance._pendingCallbacks = [callback];
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we manually inline these 3 blocks (and the one in enqueueCallback) for performance reasons?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Didn't seem worth extracting into a function.

);

if (ReactDOMFeatureFlags.useFiber) {
expected.push(['initial-callback', 'yellow']);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What happened here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Explained by the now-deleted comment above:

In Fiber, the initial callback is not enqueued until after any work scheduled by lifecycles has flushed (same semantics as a regular setState callback outside of a batch). In Stack, the initial render is scheduled inside of batchedUpdates, so the callback gets flushed right after componentDidMount.

TODO: We should fix this, in both Stack and Fiber, so that the behavior is consistent regardless of whether you're in a batch.

'Inner-setState-1',
'Inner-render-1-1',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this rerender immediately before? I thought this change would only move some setState callbacks earlier.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm actually not sure why it re-rendered immediately before, since setState is called inside componentDidUpdate which should happen in a batch regardless. Is this too significant a change to make in a non-major release?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should figure out why there was a change before we can make a meaningful decision on that point. We've made other breaking changes for Fiber so we'll need to do a 16 regardless before releasing Fiber (or maybe at the same time).

const fiber = ReactInstanceMap.get(instance);
scheduleSetState(fiber, partialState);
const priorityLevel = getPriorityContext();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did you choose to split these out rather than adding callback as an argument to scheduleSetState etc? This feels like it's asking for someone to accidentally introduce the problem this PR is fixing.

Copy link
Collaborator Author

@acdlite acdlite Dec 21, 2016

Choose a reason for hiding this comment

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

I was going to remove scheduleSetState et al from the scheduler anyway because they were just an extra layer of indirection around addUpdate et al. See @sebmarkbage's comment here for context: #8538 (comment)

To avoid introducing this problem again we could delete addCallback and enqueueCallback since they're no longer used. Sebastian thought it was better not to break the existing class updater API on the off chance someone was relying on it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rather remove them. I don't think we've ever described these as public at all. Google doesn't show anyone talking about them. I'll leave it up to you though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok then I'll remove

const fiber = ReactInstanceMap.get(instance);
scheduleSetState(fiber, partialState);
const priorityLevel = getPriorityContext();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rather remove them. I don't think we've ever described these as public at all. Google doesn't show anyone talking about them. I'll leave it up to you though.

Fixes an issue (in both Stack and Fiber) where enqueueSetState causes
a synchronous update that flushes before enqueueCallback is ever called.
Now enqueueSetState et al accept an optional callback so that both
are scheduled at the same time.
This isn't being used anymore.

Also removes addCallback from ReactFiberUpdateQueue.
@acdlite acdlite force-pushed the batchstateandcallback branch from 9f68a27 to f4c0c99 Compare December 21, 2016 19:03
@acdlite acdlite merged commit d47a3a3 into facebook:master Dec 21, 2016
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.

4 participants