Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react-router 4 - Browser history needs a DOM

I am trying server side rendering using react-router 4. I am following the example provided here https://reacttraining.com/react-router/web/guides/server-rendering/putting-it-all-together

As per the example on server we should use StaticRouter. When I import as per the example I am seeing StaticRouter as undefined

import {StaticRouter} from 'react-router';

After doing some research online I found I could use react-router-dom. Now my import statement looks like this.

import {StaticRouter} from 'react-router-dom';

However when I run the code I am getting Invariant Violation: Browser history needs a DOM in the browser.

my server.js file code

....
app.get( '*', ( req, res ) => {
  const html = fs.readFileSync(path.resolve(__dirname, '../index.html')).toString();
  const context = {};
  const markup = ReactDOMServer.renderToString(
    <StaticRouter location={req.url} context={context} >
      <App/>
    </StaticRouter>
  );

  if (context.url) {
    res.writeHead(302, {
      Location: context.url
    })
    res.end();
  } else {
      res.send(html.replace('$react', markup));
  }
} );
....

And my client/index.js code

....
ReactDOM.render((
  <BrowserRouter>
    <App />
  </BrowserRouter>
), root);
....

Update v1 Reduced my example to a bear minimum and still getting the same error.

clientIndex.js

import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from '../App'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), document.getElementById('app'))

serverIndex.js

import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from '../App'

createServer((req, res) => {
  const context = {}

  const html = ReactDOMServer.renderToString(
    <StaticRouter
      location={req.url}
      context={context}
    >
      <App/>
    </StaticRouter>
  )

res.write(`
  <!doctype html>
  <div id="app">${html}</div>
`)
res.end()
}).listen(3000);

App.js

import React from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import routes from "./client/routes";
const App = ( ) => (
  <Router>
    <Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
  </Router>
)

export default App;
like image 888
Saf Avatar asked Mar 28 '17 00:03

Saf


People also ask

How do I find history on react router v4?

Show activity on this post. import {useHistory } from "react-router-dom"; const TheContext = React. createContext(null); const App = () => { const history = useHistory(); <TheContext.

What is history dom react router?

React Router uses the history package, which builds on the browser history API to provide an interface to which we can use easily in React apps. location - (object) The current location. May have the following properties: pathname - (string) The path of the URL.

Do you need to install react router dom?

The answer is no. React Router – like the name implies – helps you route to/navigate to and render your new component in the index. html file. So as a single page application, when you navigate to a new component using React Router, the index.


3 Answers

You need to use different history provider for server side rendering because you don't have a real DOM (and browser's history) on server. So replacing BrowserRouter with Router and an alternate history provider in your app.js can resolve the issue. Also you don't have to use two wrappers. You are using BrowserRouter twice, in app.js as well as clientIndex.js which is unnecessary.

import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';

const history = createMemoryHistory();

  <Router history={history}>
   <Route path="/" exact render={( props ) => ( <div>Helloworld</div> )} />
  </Router>

You can now replace StaticRouter with ConnectedRouter which can be used both in client and server. I use the following code to choose between history and export it to be used in ConnectedRouter's history.

export default (url = '/') => {
// Create a history depending on the environment
  const history = isServer
    ? createMemoryHistory({
        initialEntries: [url]
     })
   : createBrowserHistory();
}
like image 85
Rohith Rajasekharan Avatar answered Oct 13 '22 16:10

Rohith Rajasekharan


In clientIndex.js

Rather than BrowserRouter use StaticRouter.

import { BrowserRouter } from 'react-router-dom';

import { StaticRouter } from 'react-router-dom'

like image 24
Kooldandy Avatar answered Oct 13 '22 14:10

Kooldandy


As is essentially noted in the comments, one may hit this error (as I have) by accidentally wrapping your App component in a <BrowserRouter>, when instead it is your client app that should be wrapped.

App.js

import React from 'react'

const App = () => <h1>Hello, World.</h1>

export default App

ClientApp.js

import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import ReactDOM from 'react-dom'
import App from './App'

const render = Component => {
  ReactDOM.render(
    <BrowserRouter>
      <Component />
    </BrowserRouter>,
    document.getElementById('app')
  )
}

render(App)

See also the React Router docs.

like image 4
zgreen Avatar answered Oct 13 '22 14:10

zgreen