web-dev-qa-db-fra.com

react-router 4 - L'historique du navigateur a besoin d'un DOM

J'essaie le rendu côté serveur en utilisant react-router 4. Je suis l'exemple fourni ici https://reacttraining.com/react-router/web/guides/server-rendering/putting-it-all-together

Selon l'exemple sur le serveur, nous devons utiliser StaticRouter. Lorsque j'importe selon l'exemple, je vois StaticRouter comme non défini

import {StaticRouter} from 'react-router';

Après avoir fait quelques recherches en ligne, j'ai découvert que je pouvais utiliser react-router-dom. Maintenant, ma déclaration d'importation ressemble à ceci.

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

Cependant, lorsque j'exécute le code, j'obtiens Invariant Violation: Browser history needs a DOM dans le navigateur.

mon code de fichier server.js

....
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));
  }
} );
....

Et mon code client/index.js

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

Mise à jour v1 Réduction de mon exemple au minimum et toujours la même erreur.

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;
18
Saf

Vous devez utiliser un fournisseur d'historique différent pour le rendu côté serveur car vous n'avez pas de véritable DOM (et historique du navigateur) sur le serveur. Ainsi, le remplacement de BrowserRouter par Router et un autre fournisseur d'historique dans votre app.js peut résoudre le problème. De plus, vous n'avez pas besoin d'utiliser deux wrappers. Vous utilisez BrowserRouter deux fois, dans app.js ainsi que clientIndex.js, ce qui est inutile.

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>

Vous pouvez maintenant remplacer StaticRouter par ConnectedRouter qui peut être utilisé à la fois sur le client et sur le serveur. J'utilise le code suivant pour choisir entre l'historique et l'exporter pour l'utiliser dans l'historique de ConnectedRouter.

export default (url = '/') => {
// Create a history depending on the environment
  const history = isServer
    ? createMemoryHistory({
        initialEntries: [url]
     })
   : createBrowserHistory();
}
11
Rohith Rajasekharan

Comme cela est essentiellement noté dans les commentaires, on peut frapper cette erreur (comme je l'ai fait) en enveloppant accidentellement votre composant App dans un <BrowserRouter>, alors que c'est votre application cliente qui doit être encapsulée.

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)

Voir aussi les React Router docs .

5
zgreen