-
Notifications
You must be signed in to change notification settings - Fork 46.8k
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 renderToString fails with array type children when react-dom/server render #10221
Conversation
…dom/server render
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
Can you please add an integration test verifying this works? This might be a good place. |
yes, I can, I'm going to increase right away |
hi, @gaearon, We need to set disableNewFiberFeatures = false when renderToString with array type children. But fiber can not return undefined type child when disableNewFiberFeatures = false, There are a integration test return undefined type child, so test verifying failed. There will be a conflict. Can you provide your opinion? |
This sounds right. const ReactFeatureFlags = require('ReactFeatureFlags');
ReactFeatureFlags.disableNewFiberFeatures = false; This won’t affect other tests because each test file is isolated. |
thx, Integration test has passed, I immediately resubmit pr. |
You can push a new commit to this branch and it will appear in this PR. |
Hmm. I think something got messed up with the commit because it reverts recent changes to master. If you'd like, try creating a new PR instead. |
Oh wait, no, it seems okay. Just try merging master into it.
|
Okay, it's done |
const e = await render(<AppComponent />); | ||
const parentNode = e.parentNode; | ||
expect(parentNode.childNodes[0].tagName).toBe('DIV'); | ||
expect(parentNode.childNodes[1].tagName).toBe('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.
Let's render two different types? e.g. [<div key="1" />, <p key="2" />]
.
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.
ok, good idea.
footer: '', | ||
}; | ||
if (__DEV__) { | ||
frame.debugElementStack = []; |
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.
Why do we need to do this? (Note: I'm not very familiar with this code so I'm learning it too.)
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.
"setCurrentDebugStack" method will set debugElementStack.length = 1, This is to init the debugElementStack array. The same method handles when HostCompoment return array type children.https://github.com/facebook/react/blob/master/src/renderers/shared/server/ReactPartialRenderer.js#L769
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.
Do warnings still work? You can add a test similar to this one but using an array.
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.
@gaearo, It's done. Please review.
@@ -22,7 +22,7 @@ var ReactPartialRenderer = require('ReactPartialRenderer'); | |||
*/ | |||
function renderToString(element) { | |||
invariant( | |||
React.isValidElement(element), | |||
Array.isArray(element) || React.isValidElement(element), |
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'm not sure this is sufficient. For example it could be a nested array. Conversely, it could be an array of invalid things (e.g. array of objects), and we'd want to throw in that case.
I think the validation should be moved to a further stage at the point where we actually process the next frame. Which is the same strategy we do with Fiber. This way we don’t do validation immediately, but we throw if we meet something invalid.
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'm not sure this is sufficient. For example it could be a nested array. Conversely, it could be an array of invalid things (e.g. array of objects), and we'd want to throw in that case.
I think here can use 'React.Children.toArray' method to deal with this situation.
- nested array: Will flatten the array.
[[<Hello />,<Dan />],<Cool />] => [<Hello />,<Dan />,<Cool />]
- array of objects: Will throw when passed object does not have an iterator.
I think the validation should be moved to a further stage at the point where we actually process the next frame. Which is the same strategy we do with Fiber. This way we don’t do validation immediately, but we throw if we meet something invalid.
if disableNewFiberFeatures=false then don't do validation immediately.
@@ -333,16 +330,18 @@ describe('ReactDOMServerIntegration', () => { | |||
}); | |||
|
|||
if (ReactDOMFeatureFlags.useFiber) { | |||
itRenders('a array type children as a child', async render => { | |||
class AppComponent extends React.Component { |
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.
We still need this test because it tests both server and client.
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.
hmm. Has been resolved, It can test passed both server and client. There is no warning.
Also, don't forget about iterables that are not arrays. |
@sebmarkbage , Thanks for reminding, "React.Children.toArray" might be a good method use to iterate that are not arrays. |
Looks like CI is failing, can you fix this? |
done, @gaearon , Ci is passed! |
@@ -487,6 +482,20 @@ class ReactDOMServerRenderer { | |||
if (child === null || child === false) { | |||
return ''; | |||
} else { | |||
var children = toArray(child); | |||
if (children.length > 1) { |
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 breaks for arrays returning one element.
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.
For example
function Foo() {
return [<div />];
}
crashes.
@@ -487,6 +482,20 @@ class ReactDOMServerRenderer { | |||
if (child === null || child === false) { | |||
return ''; | |||
} else { | |||
var children = toArray(child); |
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.
Let's not call toArray()
unless we know this is not a single element. The case where single element is returned should be a fast one. For example return <Foo />
shouldn't wrap <Foo />
into array before traversing down.
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.
- We need to fix the single array item case
- We need to not call
toArray()
for single-child-without-array case
I think the right way to restructure the conditions is:
} else if (isValidElement(child)) {
return this.renderDOM(child, context);
} else {
var children = toArray(child);
// ...
Please also add test cases for the single array element, and for nested arrays.
We also need tests that verify that plain objects as children throw. Similar to this test. Maybe you can put it in the same place and add |
👍 Thanks you for pointing, I understand what you mean, I will adjust these case as soon as. |
Changed done, Please review. @gaearon |
invariant( | ||
React.isValidElement(element), | ||
'renderToStream(): You must pass a valid ReactElement.', | ||
); | ||
return new ReactMarkupReadableStream(element, false); |
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.
Seems like we also need to remove this check from renderToStaticStream
as well?
if (ReactDOMFeatureFlags.useFiber) { | ||
itRenders('a array type children as a child', async render => { | ||
let Header = props => { | ||
return <p>header</p>; |
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.
Let's also make the component return array as well. This way we verify it's supported at multiple levels rather than just top level.
expect(parent.childNodes[1].tagName).toBe('SPAN'); | ||
}); | ||
|
||
it('throws if a plain object is used as a child when using SSR', async () => { |
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.
Let's move this test to ReactComponent-test
close to the non-SSR similar test.
); | ||
}); | ||
|
||
it('throws if a plain object even if it is in an owner when using SSR', async () => { |
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.
Same, let's move it close to identical non-SSR test.
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.
Minor changes.
Changed done, Please review. @gaearon . sorry, I forgot to set description when committing . 😸 |
Thank you so much! |
😄 I am very happy that the pr merged, I am grateful for your help. I will continue to study and submit more pr. |
fix for #10212 , server renderer can support when returning array in render function