Использование ReactDOMServer.renderToString с реквизитом

#node.js #reactjs #express #server-side-rendering

#node.js #reactjs #экспресс #рендеринг на стороне сервера

Вопрос:

Вся цель использования SSR — скрыть данные о местоположении от клиента. Я хочу использовать объект location, который я получаю из функции findLocation, и передать его в приложение react. Я пытаюсь передать его как реквизит, но это плохо работает. Я получаю сообщение об ошибке в консоли браузера: «Ошибка типа: не удается прочитать свойство ‘lon’ неопределенного значения в (index.js:18)». Пример вывода из местоположения печати в server.js и в app.js на клиенте :

 [nodemon] starting `node server/index.js`
SSR running on port 8080
{
  xid: 'W89710856',
  name: 'RBS',
  dist: 62.66979048,
  rate: 5,
  osm: 'way/89710856',
  kinds: 'historic_architecture,architecture,interesting_places,bank,banks,tourist_facilities,other_buildings_and_structures',
  point: { lon: -1.900748, lat: 52.481361 },
  cityDetails: {
    name: 'Birmingham',
    country: 'GB',
    lat: 52.48142,
    lon: -1.89983,
    population: 984333,
    timezone: 'Europe/London',
    status: 'OK'
  }
}
{
  xid: 'W89710856',
  name: 'RBS',
  dist: 62.66979048,
  rate: 5,
  osm: 'way/89710856',
  kinds: 'historic_architecture,architecture,interesting_places,bank,banks,tourist_facilities,other_buildings_and_structures',
  point: { lon: -1.900748, lat: 52.481361 },
  cityDetails: {
    name: 'Birmingham',
    country: 'GB',
    lat: 52.48142,
    lon: -1.89983,
    population: 984333,
    timezone: 'Europe/London',
    status: 'OK'
  }
} from the client
 

index.js для сервера:

 const path = require("path");
const fs = require("fs");
const express = require("express");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
import App from "../src/App";
import { getCities } from "./external/cities.js";
import { useOpenTripMap } from "./external/openTripMap.js";
 
const PORT = 8080;
const app = express();
const router = express.Router();
 
const citiesArray = getCities();
const radius = 1000;
// locations results limit
const limit = 20;
 
const findLocation = async () => {
  try {
    const cities = await citiesArray;
    let random = Math.floor(Math.random() * cities.length);
    const city = await useOpenTripMap(
      "geoname",
      encodeURI("name="   cities[random].name)
    );
    const locations = await useOpenTripMap(
      "radius",
      encodeURI(
        `radius=${radius}amp;limit=${limit}amp;lon=${city.lon}amp;lat=${city.lat}amp;format=json`
      )
    );
    if (!Array.isArray(locations) || !locations.length) {
      return findLocation();
    }
    random = Math.floor(Math.random() * locations.length);
    return locations[random].name
      ? {
          ...locations[random],
          cityDetails: { ...city },
        }
      : findLocation();
  } catch (error) {
    console.error(error);
  }
};
 
const serverRenderer = (req, res, next) => {
  fs.readFile(path.resolve("./build/index.html"), "utf8", (err, data) => {
    if (err) {
      console.error(err);
      return res.status(500).send("An error occurred");
    }
    findLocation()
      .then((location) => {
        console.log(location); / a valid object with a point property
        return res.send(
          data.replace(
            '<div id="root"></div>',
            `<div id="root">${ReactDOMServer.renderToString(
              <App location={location} />
            )}</div>`
          )
        );
      })
      .catch((error) => console.log(error));
  });
};
router.use("^/$", serverRenderer);
 
router.use(express.static(path.resolve(__dirname, "..", "build")));
 
app.use(router);
 
app.listen(PORT, () => {
  console.log(`SSR running on port ${PORT}`);
});
 

index.js для приложения react:

 import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

const mountElement = document.getElementById("root");
const reactMountFn =
  mountElement?.childElementCount === 0 ? ReactDOM.render : ReactDOM.hydrate;
reactMountFn(<App />, mountElement);
 

app.js в папке src в приложении react:

 import React, { useState, useEffect } from "react";
import Map from "./components/map";

function App({ location }) {
  console.log(location, "from the client");
  return (
    <div className="app">
      <Map point={location?.point} />
    </div>
  );
}

export default App;
 

Комментарии:

1. Если вы выходите из cities системы и city после их инициализации, определены ли они оба?

2. @ZacAnger Да. Я получаю массив объектов cities из «cities» и объект city из «city».

Ответ №1:

Кажется Map , что компонент выдает вам ошибку, поскольку вы не предоставляете App компоненту объект location (и point , следовательно, реквизит Map не определен) на стороне клиента:

 // index.js
...

reactMountFn(<App />, mountElement); // <-- App's location prop is undefined

 

В качестве быстрого решения я бы сделал следующее:

 function App({ location }) {
  return (
    <div className="app">
      <Map point={location ? location.point : {}} />
    </div>
  );
}

export default App;
 

React, вероятно, выдаст вам предупреждение о несоответствиях между разметками клиента и сервера. В этом случае вам нужно будет ввести переменные на стороне сервера (например, location object) в результирующий HTML и использовать их на стороне клиента. Пожалуйста, обратитесь к этому руководству для получения более подробной информации.

Например, на стороне сервера index.js:

 ...

return res.send(
  data.replace(
    '<div id="root"></div>',
    `<script>window.__LOCATION__=${JSON.stringify(location)}</script>
    <div id="root">
      ${ReactDOMServer.renderToString(
        <App location={location} />
      )}
    </div>`
  )
);
...
 

На стороне клиента index.js:

 // Get the location from a global variable injected into the server generated HTML
const location = JSON.parse(window.__LOCATION__);
reactMountFn(<App location={location} />, mountElement);

 

Комментарии:

1. Сейчас я не получаю никаких ошибок, но я не могу использовать точку местоположения с сервера. Я не использую redux, поэтому я не понимаю, как использовать эту ссылку для решения проблемы.

2. Не имеет значения, используете ли вы redux или нет. Вы можете ввести любую переменную в свой шаблон, используя тот же принцип. Добавлен короткий пример к моему ответу, пожалуйста, ознакомьтесь с ним.

3. Хорошо, я понял, но теперь это не имеет смысла, потому что целью было скрыть данные о местоположении от клиента, и теперь они видны.

4. Тогда вы не должны отображать компоненты, которые используют данные о местоположении. В противном случае вам придется передавать местоположение со стороны сервера.

5. Как это сделать? В этом весь смысл того, что я пытаюсь сделать с самого начала, получить карту с местоположением, рассчитанным на сервере, не предоставляя ее клиенту.