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

fix renderToString fails with array type children when react-dom/server render #10221

Merged
merged 27 commits into from
Jul 31, 2017
Merged

Conversation

evilbs
Copy link
Contributor

@evilbs evilbs commented Jul 19, 2017

fix for #10212 , server renderer can support when returning array in render function

@facebook-github-bot
Copy link

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!

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@gaearon
Copy link
Collaborator

gaearon commented Jul 19, 2017

Can you please add an integration test verifying this works? This might be a good place.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 19, 2017

yes, I can, I'm going to increase right away

@evilbs
Copy link
Contributor Author

evilbs commented Jul 20, 2017

hi, @gaearon

We need to set disableNewFiberFeatures = false when renderToString with array type children.
This is reference code.

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.
checking undefined type child
ReactDOMProduction-test.js

Can you provide your opinion?

@gaearon
Copy link
Collaborator

gaearon commented Jul 20, 2017

We need to set disableNewFiberFeatures = false when renderToString with array type children.

This sounds right.
I think you can set it on this line:

    const ReactFeatureFlags = require('ReactFeatureFlags');
    ReactFeatureFlags.disableNewFiberFeatures = false;

This won’t affect other tests because each test file is isolated.

Here is an example test file that already does this.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 20, 2017

thx, Integration test has passed, I immediately resubmit pr.

@gaearon
Copy link
Collaborator

gaearon commented Jul 20, 2017

You can push a new commit to this branch and it will appear in this PR.

@gaearon
Copy link
Collaborator

gaearon commented Jul 20, 2017

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.

@gaearon
Copy link
Collaborator

gaearon commented Jul 20, 2017

Oh wait, no, it seems okay. Just try merging master into it.

git fetch origin
git merge origin/master
git push

@evilbs
Copy link
Contributor Author

evilbs commented Jul 20, 2017

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');
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 render two different types? e.g. [<div key="1" />, <p key="2" />].

Copy link
Contributor Author

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 = [];
Copy link
Collaborator

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.)

Copy link
Contributor Author

@evilbs evilbs Jul 20, 2017

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

Copy link
Collaborator

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.

Copy link
Contributor Author

@evilbs evilbs Jul 20, 2017

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),
Copy link
Collaborator

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.

Copy link
Contributor Author

@evilbs evilbs Jul 21, 2017

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.

  1. nested array: Will flatten the array.
    [[<Hello />,<Dan />],<Cool />] => [<Hello />,<Dan />,<Cool />]
  2. 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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

@evilbs evilbs Jul 21, 2017

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.

@sebmarkbage
Copy link
Collaborator

Also, don't forget about iterables that are not arrays.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 21, 2017

@sebmarkbage , Thanks for reminding, "React.Children.toArray" might be a good method use to iterate that are not arrays.

@gaearon
Copy link
Collaborator

gaearon commented Jul 27, 2017

Looks like CI is failing, can you fix this?

@evilbs
Copy link
Contributor Author

evilbs commented Jul 28, 2017

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) {
Copy link
Collaborator

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.

Copy link
Collaborator

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);
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 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.

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.

  • 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.

@gaearon
Copy link
Collaborator

gaearon commented Jul 28, 2017

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 (ssr) to its name.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 29, 2017

👍 Thanks you for pointing, I understand what you mean, I will adjust these case as soon as.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 30, 2017

Changed done, Please review. @gaearon

invariant(
React.isValidElement(element),
'renderToStream(): You must pass a valid ReactElement.',
);
return new ReactMarkupReadableStream(element, false);
Copy link
Collaborator

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>;
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 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 () => {
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 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 () => {
Copy link
Collaborator

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.

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.

Minor changes.

@evilbs
Copy link
Contributor Author

evilbs commented Jul 31, 2017

Changed done, Please review. @gaearon .

sorry, I forgot to set description when committing . 😸

@gaearon gaearon merged commit f732fc6 into facebook:master Jul 31, 2017
@gaearon
Copy link
Collaborator

gaearon commented Jul 31, 2017

Thank you so much!

@evilbs
Copy link
Contributor Author

evilbs commented Aug 1, 2017

😄 I am very happy that the pr merged, I am grateful for your help. I will continue to study and submit more pr.

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