-
Notifications
You must be signed in to change notification settings - Fork 49.2k
Fix ReactFiberReconciler annotation to include PI
#8751
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
Fix ReactFiberReconciler annotation to include PI
#8751
Conversation
@@ -189,7 +189,7 @@ var TestRenderer = ReactFiberReconciler({ | |||
|
|||
useSyncScheduling: true, | |||
|
|||
getPublicInstance(inst) { | |||
getPublicInstance(inst : Instance | TextInstance) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have the return value here too to guarantee that the inference is correct. Otherwise, if it gets passed somewhere else, it might be inferred to be something very simple (like an empty object) and passes everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do I say the return value is Instance | TextInstance | createNodeMockReturnValue
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Just put getPublicInstance(inst : Instance | TextInstance) : *
then for "inferred". That way if createNodeMock gets a proper type in the future, it'll get caught.
@@ -170,7 +170,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, T | |||
|
|||
deferredUpdates, | |||
|
|||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | TI | null) { | |||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | TI | PI | null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this method is the same as for refs except for the return value on the very top level. var inst = ReactDOM.render(...)
It should never be possible to get a handle on an instance of I
or TI
since those are internal instances to the renderer. The point of getPublicInstance
is to ensure that.
Therefore this should only return ReactComponent<any, any, any> | PI | null
.
To ensure that you must call getPublicInstance
on the stateNode
for the appropriate tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments.
9d42ea0
to
41bdb14
Compare
Updated @sebmarkbage. Thanks! |
@@ -170,7 +170,7 @@ module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, T | |||
|
|||
deferredUpdates, | |||
|
|||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | TI | null) { | |||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | PI | null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function still needs to pass stateNode
to getPublicInstance for the relevant tags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how could I reproduce this scenario? None of the current tests seem to have an issue. I attempted to reproduce it with a root ref and the return value of render
with a few component types.
describe('root level refs', () => {
it('attaches and detaches root refs in stack', () => {
assertForRenderer(require('ReactDOM'));
});
it('attaches and detaches root refs in fiber', () => {
assertForRenderer(require('ReactDOMFiber'));
});
const assertForRenderer = (DOM) => {
spyOn(console, 'error');
var ref = jest.fn();
var container = document.createElement('div');
var result = DOM.render(<div ref={ref} />, container);
expect(ref).toHaveBeenCalledTimes(1);
expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement);
expect(result).toBe(ref.mock.calls[0][0]);
DOM.unmountComponentAtNode(container);
expect(ref).toHaveBeenCalledTimes(2);
expect(ref.mock.calls[1][0]).toBe(null);
class Comp extends React.Component {
render() {
return <div>Comp</div>;
}
}
ref = jest.fn();
result = DOM.render(<Comp ref={ref}/>, container);
expect(ref).toHaveBeenCalledTimes(1);
expect(ref.mock.calls[0][0]).toBeInstanceOf(Comp);
expect(result).toBe(ref.mock.calls[0][0]);
DOM.unmountComponentAtNode(container);
expect(ref).toHaveBeenCalledTimes(2);
expect(ref.mock.calls[1][0]).toBe(null);
};
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think that getPublicRootInstance
will ever call getPublicInstance
. They both seem to be added for backwards compatibility with different features and operate independently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, what should this be:
var result = ReactDOMFiber.render(5, container);
// result? '5' or fiber?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public API must never expose fibers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this exists for Stack compat I would return nothing. Stack doesn't allow rendering top-level text nodes so nothing would break if we didn't implement this.
If we did want to implement it (I don't see why) then it would make sense to return the TextInstance (text node), similar to how ReactDOM.render(<div />)
returns the Instance (div).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is “current” behavior. Even if I remove the getPublicInstance
added from the ref, the result in that example is not '5' as I expected. I think, though, that that is a different issue..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about this the less I’m convinced that this should be changed from I | TI
to PI
. PI
was only added for getPublicInstance
which is only called via attachRef
. The naming may be unfortunate since getHostPublicInstance
and getPublicInstance
aren’t related (internally).
Fwiw, I’m not attempting or desiring to implement anything new here, just trying to understand what the behavior was before #8628.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var instA;
var instB = MyRenderer.render(<div ref={n => instA = n} />);
expect(instA).toBe(instB);
Is that always true with the test renderer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah. That’d do it. Test case and possible solution implemented in 9a9a4db.
e6f2186
to
2a705c6
Compare
2a705c6
to
f78245f
Compare
f78245f
to
9a9a4db
Compare
const root : FiberRoot = (container.stateNode : any); | ||
const containerFiber = root.current; | ||
if (!containerFiber.child) { | ||
return null; | ||
} | ||
return containerFiber.child.stateNode; | ||
return getPublicInstance(containerFiber.child.stateNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still suffers from this problem: https://github.com/facebook/react/pull/8628/files/9a9276b3fbcec4cfb6d5c69e891c5feafe0195f2#r95653360 Which remains a problem in commitWork.
You need to check whether containerFiber.child.tag === HostComponent
before passing it to getPublicInstance.
Otherwise, in the case of render(<Foo />)
you will pass an instance of Foo
to getPublicInstance
which is neither an I
nor TI
so the function doesn't know what to do with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will become more clear after #8545 because then the stateNode
will have a proper type and Flow would've disallowed passing it to getPublicInstance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/facebook/react/pull/8751/files#diff-7002876230f7effe9aa22e292f6a52c6R463 handles it in CommitWork. Added the switch for this method as well to do the same for getRootPublicInstance
. Some tests have regressed that I’ll need to look into why.
Shouldn’t render(<Foo />)
pass the I | TI
back for backwards compatibility?
9a9a4db
to
5156c65
Compare
}); | ||
|
||
it('attaches and detaches root refs in fiber', () => { | ||
assertForRenderer('ReactDOMFiber'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally we try to avoid code that is as dynamic as this. It makes it harder to trace what's going on.
I think this might be ready for another look. |
switch (containerFiber.child.tag) { | ||
case HostComponent: | ||
return getPublicInstance(containerFiber.child.stateNode); | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case should match on ClassComponent
only. It won't type check if we use proper disjoint unions on Fibers.
That type error would be correct since this can be other types such as fragments, coroutines etc. Those should do something reasonable like throw, drill down (like findDOMNode does) or return null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It won't type check if we use proper disjoint unions on Fibers.
Is that PR any closer to landing by chance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll have to be a massive rebase. We could try it again though. There are some new features in Flow that can make that palatable now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accepting because it is no more buggy than what is already there but I left a comment.
OK. I merged and added followup as a child of existing todo in #7925. |
Fixing up @sebmarkbage comments at #8628 (comment)