Skip to content

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Nov 11, 2016

Known issues:

  • Passing nextContext to componentDidUpdate() would require changing the approach and adding a field to Fiber. For now we decided to bail on supporting this use case (e.g. Facebook doesn't have a single component in thousands using this pattern). We'll probably make a breaking change in Stack as well for this in 16.
  • Deep updates blocked by sCU still don't work: this is about feature parity.
  • It might be broken with error boundaries. I will address this in later PRs.

@@ -22,8 +22,7 @@ src/addons/transitions/__tests__/ReactTransitionGroup-test.js
* should warn for duplicated keys with component stack info

src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should filter out context not in contextTypes
* should filter context properly in callbacks
* should pass previous context to lifecycles
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a new PR I extracted from "should filter context properly in callbacks". It covers the componentDidUpdate with nextContext which we punted on.

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 meant test, not PR

@edvinerikson
Copy link
Contributor

This looks much cleaner than my approach, awesome 😄

@@ -182,11 +189,14 @@ module.exports = function<T, P, I, TI, C>(
} else {
shouldUpdate = updateClassInstance(current, workInProgress);
}
const instance = workInProgress.stateNode;
if (typeof instance.getChildContext === 'function') {
pushContextProvider(workInProgress);
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 should probably do this after rendering. Otherwise context provider would likely see its own context.

@gaearon
Copy link
Collaborator Author

gaearon commented Nov 14, 2016

Moved the implementation into scheduler to solve a bug with reusing work.
You can follow along in the commits but the final result may be easier to look at as a full PR.

We mistakingly forget to push the context if the parent is being reused but the child is not.
It is error-prone to push and pop context in begin or complete phases because of all the bailouts. Scheduler has enough knowledge to see if pushing is necessary because it knows when we go inside or outside the tree.
@@ -267,6 +274,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
const current = workInProgress.alternate;
const next = completeWork(current, workInProgress);

// We are leaving this subtree, so pop context if any.
if (isContextProvider(workInProgress)) {
popContextProvider();
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was hoping that we would be able to put push/pop inside ReactFiberBeginWork/ReactFiberCompleteWork. The reason is that I'd like to be able to reuse the tag check inside the switch statement so that we only even check for this if we hit the ClassComponent branch. That way when we build optimized paths, they can completely skip this check.

I realize that this might be currently broken because we bail out before the switch statement in certain cases right now. We might have to move those around.

checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
}

const mergedContext = Object.assign({}, getUnmaskedContext(), childContext);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use spread with our Object.assign rewrite?

As per discussion, this is better because then this code only runs for the class type of work. We will still need to fix bailouts for deep setState calls.

let index = -1;
const contextStack : Array<Object> = [];
const didPerformWorkStack : Array<boolean> = [];
Copy link
Collaborator

@acdlite acdlite Nov 15, 2016

Choose a reason for hiding this comment

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

Could these arrays be combined into a single linked list? From what I can tell it doesn't look like you need indexing because you only ever need to access the tail.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In this case it'll (probably) be more efficient to use an array since we won't need to reorder anything and we won't need to reallocate anything when it grows and shrinks.

We can avoid recreating the merged context object if the context provider work was reused. This is essential to avoid allocations for deep setState() calls.
type.name || (constructor && constructor.name) ||
'A Component'
);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

We already have the getComponentName module that you could use:

https://github.com/facebook/react/blob/master/src/shared/utils/getComponentName.js#L18-L38

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 tried but it was intentionally DEV-only at the time, and I need it for an invariant.
If we're ok increasing non-Fiber size with Fiber code before Fiber is ready, I could remove the DEV gate there.

@@ -125,6 +129,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
return null;
case ClassComponent:
transferOutput(workInProgress.child, workInProgress);
// We are leaving this subtree, so pop context if any.
if (isContextProvider(workInProgress)) {
popContextProvider();
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we unwind to catch an error, we need to unwind the context as well so that we can continue with the next sibling after the error boundary.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was thinking instead of literally unwinding to catch an error, we schedule an update on the boundary and reset the unit of work pointer. That's how I've started to implement it, not totally sure if it will work yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Where by "schedule an update" I mean give it work priority

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not relevant now though, is it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I'm trying to say is that if we always start from the root, I don't think unwinding the context is necessary because of your line here: https://github.com/facebook/react/pull/8272/files#diff-74dedebbf08d8dcec2e92b8931274454R377

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea that could work.

This matches Stack behavior.
Had to rewrite a test that used Simulate because Fiber doesn't support events yet.
Also changed tests for componentDidUpdate() since Fiber intentionally doesn't pass prevContext argument.
@gaearon gaearon merged commit 4029ccd into facebook:master Nov 15, 2016
@gaearon gaearon deleted the fiber-context branch November 15, 2016 17:14
@@ -249,13 +262,15 @@ module.exports = function<T, P, I, TI, C>(
}
var fn = workInProgress.type;
var props = workInProgress.pendingProps;
var context = getMaskedContext(workInProgress);

var value;

if (__DEV__) {

Choose a reason for hiding this comment

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

what the reason of such style?
why here you can't just have

    var value = fn(props, context);

    if (__DEV__) {
      ReactCurrentOwner.current = workInProgress;
    }

or

    if (__DEV__) {
      ReactCurrentOwner.current = workInProgress;
    }
    var value = fn(props, context);

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 don't think there's any reason for this, putting just current owner assignment in a condition would be equivalent.

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.

6 participants