ReactDOMServer.renderToString() - What exactly does it do?

Viewed 1994

I know it just renders HTML as text, so speaking of class components, only contructor() and render() methods are invoked during the renderToString(). But I've noticed some weird behavior with regards to function components. Consider the following component example:


function MyComponent() {

  console.log(1);

  return(
    <React.Fragment>
      Some text
    </React.Fragment>
  )
}

I just use console.log() inside of function component, which, I guess, is equivalent to constructor() method inside of a class component.

renderToString() is invoked inside express route handler:

import { ServerStyleSheets, ThemeProvider } from '@material-ui/core/styles';
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import theme from 'mui/theme';
import App from 'components/App';

const SERVER_PORT = 8081; // Port for Express to listen

const app = express();

app.get('*', async (req, res) => {
  const sheets = new ServerStyleSheets();

  const inlineApp = renderToString(
    sheets.collect(
      <Provider store={exampleStore}>
        <ThemeProvider theme={theme}>
          <CssBaseline />
          <App />
        </ThemeProvider>
      </Provider>,
    ),
  );

  const css = sheets.toString();

  const renderedHTML = `
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <style id="jss">${css}</style>
        <title>Repl.it</title>
      </head>
      <body>
        <div id="root">${inlineApp}</div>
        <script>
        window.STATE = ${JSON.stringify(exampleStore.getState())};
        </script>
        <script src="app.js"></script>
      </body>
    </html>
    `;

  res.send(renderedHTML);
});

app.listen(SERVER_PORT, () => {
  console.log(`Listening at http://localhost:${SERVER_PORT}`);
});

The problem is I'm getting the console output twice when invoking ReactDOMServer.renderToString() once. I dont use double rendering on server side. So I have two questions:

  • Can you explain what's exactly happening here?

  • If it is a correct behavior, then is there a way to run some code only once during the renderToString()?

2 Answers

Not enough data to make a definitive diagnosis, but may it be that you run in React's strict mode?

Take a look at this: https://reactjs.org/docs/strict-mode.html

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

If that's not the case... and I don't actually see <StrictMode> in your code, then it'd be nice if you'd make a minimal reproducible example.


Also note, that while function component bodies invoked twice in strict mode, console.log()s, starting from React 17, are overwritten so that you should see only one record on the console. But that can change. Not everybody happy with that and now there is a PR to at least opt-out from this behavior.

What is happening here might be the play of React's Strict mode.

React app when running in strict mode, intentionally double invokes certain functions (e.g. the component's constructor in this case), in order to make it easier to spot and identify easy to miss side-effects during the development phase, which may create issue in production.

You can read about react strict mode in detail here - Strict Mode

However it's important to know that Strict Mode double invokes functions only in development environment and not in Production.

Related