Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
Restore getDataFromTree(rootElement, rootContext) API. (#2586)
Browse files Browse the repository at this point in the history
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 authored Nov 14, 2018
1 parent cb225f5 commit 99a846f
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 38 deletions.
29 changes: 29 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,35 @@
necessary anymore.
[PR #2533](https://github.com/apollographql/react-apollo/pull/2533)

- Restore original `getDataFromTree(tree, context)` API, and introduce a
new function called `getMarkupFromTree` to enable custom rendering
functions:
```typescript
export default function getDataFromTree(
tree: React.ReactNode,
context: { [key: string]: any } = {},
) {
return getMarkupFromTree({
tree,
context,
renderFunction: renderToStaticMarkup,
});
}

export type GetMarkupFromTreeOptions = {
tree: React.ReactNode;
context?: { [key: string]: any };
renderFunction?: typeof renderToStaticMarkup;
};

export function getMarkupFromTree({
tree,
context = {},
renderFunction = renderToStaticMarkup,
}: GetMarkupFromTreeOptions): Promise<string> {...}
```
[PR #2586](https://github.com/apollographql/react-apollo/pull/2586)

## 2.2.4 (October 2, 2018)

### Bug Fixes
Expand Down
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

0 comments on commit 99a846f

Please sign in to comment.