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(ServerRendering): execution should be async #982

Merged
merged 2 commits into from
Feb 4, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
* `"use strict";`
* 80 character line length
* "Attractive"
* Do not use the optional parameters of `setTimeout` and `setInterval`

## License

Expand Down
101 changes: 47 additions & 54 deletions src/core/__tests__/ReactRenderDocument-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,13 @@ describe('rendering React components at document', function() {
}
});

React.renderComponentToString(<Root />, function(markup) {
testDocument = getTestDocument(markup);
var component = React.renderComponent(<Root />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');
var markup = React.renderComponentToString(<Root />);
testDocument = getTestDocument(markup);
var component = React.renderComponent(<Root />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');

var componentID = ReactMount.getReactRootID(testDocument);
expect(componentID).toBe(component._rootNodeID);
});
var componentID = ReactMount.getReactRootID(testDocument);
expect(componentID).toBe(component._rootNodeID);
});

it('should not be able to unmount component from document node', function() {
Expand All @@ -92,17 +91,16 @@ describe('rendering React components at document', function() {
}
});

React.renderComponentToString(<Root />, function(markup) {
testDocument = getTestDocument(markup);
React.renderComponent(<Root />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');
var markup = React.renderComponentToString(<Root />);
testDocument = getTestDocument(markup);
React.renderComponent(<Root />, testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');

expect(function() {
React.unmountComponentAtNode(testDocument);
}).toThrow(UNMOUNT_INVARIANT_MESSAGE);
expect(function() {
React.unmountComponentAtNode(testDocument);
}).toThrow(UNMOUNT_INVARIANT_MESSAGE);

expect(testDocument.body.innerHTML).toBe('Hello world');
});
expect(testDocument.body.innerHTML).toBe('Hello world');
});

it('should not be able to switch root constructors', function() {
Expand Down Expand Up @@ -138,20 +136,19 @@ describe('rendering React components at document', function() {
}
});

React.renderComponentToString(<Component />, function(markup) {
testDocument = getTestDocument(markup);
var markup = React.renderComponentToString(<Component />);
testDocument = getTestDocument(markup);

React.renderComponent(<Component />, testDocument);
React.renderComponent(<Component />, testDocument);

expect(testDocument.body.innerHTML).toBe('Hello world');
expect(testDocument.body.innerHTML).toBe('Hello world');

// Reactive update
expect(function() {
React.renderComponent(<Component2 />, testDocument);
}).toThrow(UNMOUNT_INVARIANT_MESSAGE);
// Reactive update
expect(function() {
React.renderComponent(<Component2 />, testDocument);
}).toThrow(UNMOUNT_INVARIANT_MESSAGE);

expect(testDocument.body.innerHTML).toBe('Hello world');
});
expect(testDocument.body.innerHTML).toBe('Hello world');
});

it('should be able to mount into document', function() {
Expand All @@ -172,16 +169,14 @@ describe('rendering React components at document', function() {
}
});

React.renderComponentToString(
<Component text="Hello world" />,
function(markup) {
testDocument = getTestDocument(markup);
var markup = React.renderComponentToString(
<Component text="Hello world" />
);
testDocument = getTestDocument(markup);

React.renderComponent(<Component text="Hello world" />, testDocument);
React.renderComponent(<Component text="Hello world" />, testDocument);

expect(testDocument.body.innerHTML).toBe('Hello world');
}
);
expect(testDocument.body.innerHTML).toBe('Hello world');
});

it('should give helpful errors on state desync', function() {
Expand All @@ -202,26 +197,24 @@ describe('rendering React components at document', function() {
}
});

React.renderComponentToString(
<Component text="Goodbye world" />,
function(markup) {
testDocument = getTestDocument(markup);

expect(function() {
// Notice the text is different!
React.renderComponent(<Component text="Hello world" />, testDocument);
}).toThrow(
'Invariant Violation: ' +
'You\'re trying to render a component to the document using ' +
'server rendering but the checksum was invalid. This usually ' +
'means you rendered a different component type or props on ' +
'the client from the one on the server, or your render() methods ' +
'are impure. React cannot handle this case due to cross-browser ' +
'quirks by rendering at the document root. You should look for ' +
'environment dependent code in your components and ensure ' +
'the props are the same client and server side.'
);
}
var markup = React.renderComponentToString(
<Component text="Goodbye world" />
);
testDocument = getTestDocument(markup);

expect(function() {
// Notice the text is different!
React.renderComponent(<Component text="Hello world" />, testDocument);
}).toThrow(
'Invariant Violation: ' +
'You\'re trying to render a component to the document using ' +
'server rendering but the checksum was invalid. This usually ' +
'means you rendered a different component type or props on ' +
'the client from the one on the server, or your render() methods ' +
'are impure. React cannot handle this case due to cross-browser ' +
'quirks by rendering at the document root. You should look for ' +
'environment dependent code in your components and ensure ' +
'the props are the same client and server side.'
);
});

Expand Down
7 changes: 3 additions & 4 deletions src/core/__tests__/ReactTextComponent-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ describe('ReactTextComponent', function() {
var ThisThingShouldBeEscaped = '">>> LULZ <<<"';
var ThisThingWasBeEscaped = '&quot;&gt;&gt;&gt; LULZ &lt;&lt;&lt;&quot;';
var thing = React.DOM.div(null, React.DOM.span({key:ThisThingShouldBeEscaped}, ["LULZ"]));
React.renderComponentToString(thing, function(html){
expect(html).not.toContain(ThisThingShouldBeEscaped);
expect(html).toContain(ThisThingWasBeEscaped);
});
var html = React.renderComponentToString(thing);
expect(html).not.toContain(ThisThingShouldBeEscaped);
expect(html).toContain(ThisThingWasBeEscaped);
})
});
17 changes: 7 additions & 10 deletions src/environment/ReactServerRendering.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,27 @@ var invariant = require('invariant');

/**
* @param {ReactComponent} component
* @param {function} callback
* @return {String} the markup
Copy link
Contributor

Choose a reason for hiding this comment

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

this fails our internal typechecker. making it lowercase makes it succeed. can you make this change?

*/
function renderComponentToString(component, callback) {
// We use a callback API to keep the API async in case in the future we ever
// need it, but in reality this is a synchronous operation.

function renderComponentToString(component) {
invariant(
ReactComponent.isValidComponent(component),
'renderComponentToString(): You must pass a valid ReactComponent.'
);

invariant(
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure that this should be an invariant... do we want to stop execution or should we let it keep running and just warn? @petehunt?

Copy link
Contributor

Choose a reason for hiding this comment

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

I am cool with this

typeof callback === 'function',
'renderComponentToString(): You must pass a function as a callback.'
!(arguments.length === 2 && typeof arguments[1] === 'function'),
'renderComponentToString(): This function became synchronous and now ' +
'returns the generated markup. Please remove the second parameter.'
);

var id = ReactInstanceHandles.createReactRootID();
var transaction = ReactReconcileTransaction.getPooled();
transaction.reinitializeTransaction();
try {
transaction.perform(function() {
return transaction.perform(function() {
var markup = component.mountComponent(id, transaction, 0);
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
callback(markup);
return ReactMarkupChecksum.addChecksumToMarkup(markup);
}, null);
} finally {
ReactReconcileTransaction.release(transaction);
Expand Down
55 changes: 19 additions & 36 deletions src/environment/__tests__/ReactServerRendering-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,8 @@ describe('ReactServerRendering', function() {
});

it('should generate simple markup', function() {
var response;
ReactServerRendering.renderComponentToString(
<span>hello world</span>,
function(response_) {
response = response_;
}
var response = ReactServerRendering.renderComponentToString(
<span>hello world</span>
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
Expand All @@ -73,12 +69,10 @@ describe('ReactServerRendering', function() {
var EventPluginHub = require('EventPluginHub');
var cb = mocks.getMockFunction();

ReactServerRendering.renderComponentToString(
<span onClick={cb}>hello world</span>,
function() {
expect(EventPluginHub.__getListenerBank()).toEqual({});
}
var response = ReactServerRendering.renderComponentToString(
<span onClick={cb}>hello world</span>
);
expect(EventPluginHub.__getListenerBank()).toEqual({});
});

it('should render composite components', function() {
Expand All @@ -92,12 +86,8 @@ describe('ReactServerRendering', function() {
return <span>My name is {this.props.name}</span>;
}
});
var response;
ReactServerRendering.renderComponentToString(
<Parent />,
function(response_) {
response = response_;
}
var response = ReactServerRendering.renderComponentToString(
<Parent />
);
expect(response).toMatch(
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
Expand Down Expand Up @@ -144,13 +134,8 @@ describe('ReactServerRendering', function() {
}
});

var response;

ReactServerRendering.renderComponentToString(
<TestComponent />,
function (_response) {
response = _response;
}
var response = ReactServerRendering.renderComponentToString(
<TestComponent />
);

expect(response).toMatch(
Expand Down Expand Up @@ -212,14 +197,10 @@ describe('ReactServerRendering', function() {
expect(element.innerHTML).toEqual('');

ExecutionEnvironment.canUseDOM = false;
ReactServerRendering.renderComponentToString(
<TestComponent name="x" />,
function(markup) {
lastMarkup = markup;
}
lastMarkup = ReactServerRendering.renderComponentToString(
<TestComponent name="x" />
);
ExecutionEnvironment.canUseDOM = true;

element.innerHTML = lastMarkup + ' __sentinel__';

React.renderComponent(<TestComponent name="x" />, element);
Expand Down Expand Up @@ -250,23 +231,25 @@ describe('ReactServerRendering', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
'not a component',
function() {}
'not a component'
)
).toThrow(
'Invariant Violation: renderComponentToString(): You must pass ' +
'a valid ReactComponent.'
);
});

it('should provide guidance for breaking API changes', function() {
expect(
ReactServerRendering.renderComponentToString.bind(
ReactServerRendering,
React.DOM.div(),
'not a function'
<div />,
function(){}
)
).toThrow(
'Invariant Violation: renderComponentToString(): You must pass ' +
'a function as a callback.'
'Invariant Violation: renderComponentToString(): This function became ' +
'synchronous and now returns the generated markup. Please remove the ' +
'second parameter.'
);
});
});