Skip to content
This repository was archived by the owner on Apr 13, 2023. It is now read-only.
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
Next Next commit
Restore getDataFromTree(rootElement, rootContext) API.
In [email protected], the rootContext parameter was replaced by the
custom renderFunction parameter, which was a breaking change:
#2533 (comment)

This commit restores the previous API, while providing an alternative API
that is more flexible and more accurately named: getMarkupFromTree.

If you want to specify a custom rendering function, you should now use
getMarkupFromTree instead of getDataFromTree:

  import { getMarkupFromTree } from "react-apollo";
  import { renderToString } from "react-dom/server";

  getMarkupFromTree({
    tree: <App/>,
    // optional; defaults to {}
    context: { ... },
    // optional; defaults to renderToStaticMarkup
    renderFunction: renderToString,
  }).then(markup => {
    // Use the markup returned by the final rendering...
  });

Hopefully, since [email protected] was never assigned the `latest` tag on
npm, we can push this change without another minor version bump.

cc @evenchange4 @hwillson
  • Loading branch information
benjamn committed Nov 12, 2018
commit 2048f05053eb9848abd82dd7b8432fb6254994e5
14 changes: 8 additions & 6 deletions examples/ssr/server/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { ApolloClient } from 'apollo-client';
import { getDataFromTree, ApolloProvider } from 'react-apollo';
import { getMarkupFromTree, ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { WebApp } from 'meteor/webapp';
Expand All @@ -27,12 +27,14 @@ export const render = async sink => {
</ApolloProvider>
);

const start = +new Date;
const start = Date.now();
// Load all data from local server
await getDataFromTree(WrappedApp);
const body = renderToString(WrappedApp);
console.log("server rendering took", new Date - start, "ms");
sink.renderIntoElementById('app', body);
const markup = await getMarkupFromTree({
tree: WrappedApp,
renderFunction: renderToString,
});
console.log("server rendering took", Date.now() - start, "ms");
sink.renderIntoElementById('app', markup);
sink.appendToBody(`
<script>
window.__APOLLO_STATE__=${JSON.stringify(client.extract())};
Expand Down
76 changes: 45 additions & 31 deletions src/getDataFromTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,46 +84,60 @@ export class RenderPromises {
}
}

class RenderPromisesProvider extends React.Component<{
renderPromises: RenderPromises;
}> {
static childContextTypes = {
renderPromises: PropTypes.object,
};

getChildContext() {
return {
renderPromises: this.props.renderPromises,
};
}

render() {
return this.props.children;
}
export default function getDataFromTree(
tree: React.ReactNode,
context: { [key: string]: any } = {},
) {
return getMarkupFromTree({
tree,
context,
// If you need to configure this renderFunction, call getMarkupFromTree
// directly instead of getDataFromTree.
renderFunction: renderToStaticMarkup,
});
}

export default function getDataFromTree(
rootElement: React.ReactNode,
export type GetMarkupFromTreeOptions = {
tree: React.ReactNode;
context?: { [key: string]: any };
renderFunction?: typeof renderToStaticMarkup;
};

export function getMarkupFromTree({
tree,
context = {},
// The rendering function is configurable! We use renderToStaticMarkup as
// the default, because it's a little less expensive than renderToString,
// and legacy usage of getDataFromTree ignores the return value anyway.
renderFunction = renderToStaticMarkup,
): Promise<string> {
}: GetMarkupFromTreeOptions): Promise<string> {
const renderPromises = new RenderPromises();

function process(): Promise<string> | string {
const html = renderFunction(
React.createElement(RenderPromisesProvider, {
renderPromises,
// Always re-render from the rootElement, even though it might seem
// better to render the children of the component responsible for the
// promise, because it is not possible to reconstruct the full context
// of the original rendering (including all unknown context provider
// elements) for a subtree of the orginal component tree.
children: rootElement,
})
);
class RenderPromisesProvider extends React.Component {
static childContextTypes: { [key: string]: any } = {
renderPromises: PropTypes.object,
};

getChildContext() {
return { ...context, renderPromises };
}

render() {
// Always re-render from the rootElement, even though it might seem
// better to render the children of the component responsible for the
// promise, because it is not possible to reconstruct the full context
// of the original rendering (including all unknown context provider
// elements) for a subtree of the orginal component tree.
return tree;
}
}

Object.keys(context).forEach(key => {
RenderPromisesProvider.childContextTypes[key] = PropTypes.any;
});

function process(): Promise<string> | string {
const html = renderFunction(React.createElement(RenderPromisesProvider));
return renderPromises.hasPromises()
? renderPromises.consumeAndAwaitPromises().then(process)
: html;
Expand Down
24 changes: 23 additions & 1 deletion test/client/getDataFromTree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ApolloProvider,
walkTree,
getDataFromTree,
getMarkupFromTree,
DataValue,
ChildProps,
} from '../../src';
Expand Down Expand Up @@ -511,13 +512,34 @@ describe('SSR', () => {
expect(markup).toMatch(/James/);
});

await getDataFromTree(app, ReactDOM.renderToString).then(html => {
await getMarkupFromTree({
tree: app,
renderFunction: ReactDOM.renderToString,
}).then(html => {
const markup = ReactDOM.renderToString(app);
expect(markup).toEqual(html);
expect(markup).toMatch(/James/);
});
});

it('should support passing a root context', () => {
class Consumer extends React.Component {
static contextTypes = {
text: PropTypes.string.isRequired,
};

render() {
return <div>{this.context.text}</div>;
}
}

return getDataFromTree(<Consumer/>, {
text: "oyez"
}).then(html => {
expect(html).toEqual('<div>oyez</div>');
});
});

it('should run through all of the queries (also defined via Query component) that want SSR', () => {
const query = gql`
{
Expand Down