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

renderToStringWithData runs twice #1361

Closed
ravilution opened this issue Nov 28, 2017 · 11 comments
Closed

renderToStringWithData runs twice #1361

ravilution opened this issue Nov 28, 2017 · 11 comments

Comments

@ravilution
Copy link

Hi,
renderToStringWithData is trying to render the supplied React element twice. Please check the below code and output. I am using node v8.9.0

package.json

{
  "name": "ssr-renders-twice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-client-preset": "^1.0.3",
    "apollo-server-express": "^1.2.0",
    "express": "^4.16.2",
    "react": "^16.2.0",
    "react-apollo": "^2.0.1",
    "react-dom": "^16.2.0"
  }
}

index.js

var React = require("React");
var express = require("express");
var renderToStringWithData = require("react-apollo").renderToStringWithData;

const app = express();

function App() {
  console.log("inside App");
  return React.createElement("div", null, "app");
}

app.get("/", async (req, res) => {
  console.log("req.url", req.url);
  const reactHTML = await renderToStringWithData(
    React.createElement(App, null)
  );
  res.send(`<!doctype html><html><body>${reactHTML}</body></html>`);
});

app.listen(3000, () => {
  console.log("started at ", new Date().toLocaleString());
});

output

started at  2017-11-28 14:07:57
req.url /
inside App
inside App
@foxCanFly
Copy link

foxCanFly commented Nov 30, 2017

The same with await getDataFromTree(bodyComponent);.

@ravilution
Copy link
Author

Because of this, when I do SSR, I always get html between if(loading){} and not on else if (data){}

@ghost
Copy link

ghost commented Feb 25, 2018

+1

@foxCanFly
Copy link

foxCanFly commented Feb 26, 2018

Maybe this could be useful. For me this was happening because of not very obvious usage of Provider which is not only pass functionality or variables inside context but wrap component in another "util" component. And that's fine. So we have to use this ApolloProvider or something like this. Before I just set context manually and had the same problem because I didn't have this additional functionality from ApolloProvide.

@ghost
Copy link

ghost commented Feb 26, 2018

@foxCanFly can you post an example? Maybe on https://codesandbox.io/?

@mkochendorfer
Copy link

I have run into the same thing causing various weird unexpected behaviors in my app due to the duel render/constructors. This occurs because getDataFromTree and ReactDOM.renderToString both construct independent instances of the application.

This can lead to really baffling behaviors if you try to build your ApolloClient instance inside of a React component instead of outside of the React app in your SSR code. Specifically it causes the SSR to fail because it builds up all of the data requests in getDataFromTree on the first render/constructor, but then it throws that app away and builds a new one in ReactDOM.renderToString with a fresh ApolloClient built in the constructors of the components again but now with none of the data from the initial tree traversal.

@lantos1618
Copy link

lantos1618 commented Oct 5, 2018

Same issue here,

reproduced at
https://github.com/LantosTG/ReactIssueSSRDoubleCall

the issue is in the renderToStringWithData function

export function renderToStringWithData(component) {
    return getDataFromTree(component).then(function () { return ReactDOM.renderToString(component); });
}

// the getDataFromTree will render the react component and then return the renderToString which renders again

this is also shown on the Wiki
https://www.apollographql.com/docs/react/features/server-side-rendering.html

import { getDataFromTree } from "react-apollo"

const client = new ApolloClient(....);

// during request (see above)
getDataFromTree(App).then(() => {
  // We are ready to render for real
  const content = ReactDOM.renderToString(App);
  const initialState = client.extract();

  const html = <Html content={content} state={initialState} />;

  res.status(200);
  res.send(`<!doctype html>\n${ReactDOM.renderToStaticMarkup(html)}`);
  res.end();
});

@hwillson
Copy link
Member

This should no longer be an issue with current day versions of react-apollo. Thanks!

@mkochendorfer
Copy link

@hwillson Can you elaborate on how this was fixed/improved?

@hwillson
Copy link
Member

@mkochendorfer Sure thing; getDataFromTree (which renderToStringWithData is using) was completely re-written in react-apollo 2.3.0. This new implementation has addressed many of the outstanding getDataFromTree / renderToStringWithData issues. Let us know if you're still running into any problems.

@mkochendorfer
Copy link

@hwillson Thanks for the extra context.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants