React server side rendering is not updating after client side route change - javascript

For the first time, the server side is refreshed, but the next time, only the client side is changed as usual, and the server does not change.
For example, with each refresh or typing of the address in the browser, the server also changes and works, but if I switch between pages on the client side with react router, the server does not change.
what is the problem?
#server/server.js
import path from 'path';
import fs from 'fs';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from '../src/App';
const PORT = 5000;
const app = express();
const router = express.Router();
app.use('/build', express.static('build'));
app.use((req, res, next) => {
if (/\.js|\.css|\.png|\.jpg|\.jpeg/.test(req.path)) {
res.redirect('/build' + req.path);
} else {
next();
}
})
app.get('*', (req, res) => {
const context = {};
const app = ReactDOMServer.renderToString(
<StaticRouter location={req.path} context={context}>
<App />
</StaticRouter>
);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf-8', (err, data) => {
if (err) {
console.log("Something went wrong:", err);
return res.status(500).send("Oops, better luck next time!");
}
return res.send(data.replace('<div id="root"></div>', `<div id="root">${app}</div>`));
});
});
router.use(express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '10d' }));
app.use(router);
app.listen(PORT, () => {
console.log(`SSR running on ${PORT}`);
});
#server/index.js
require('ignore-styles');
require('#babel/register')({
ignore: [/(node_module)/],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: ['#babel/transform-runtime'],
});
require('./server');
#index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './App';
ReactDOM.hydrate(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);

There is nothing wrong with your Server Side Rendering setup. This is what's going on under the hood.
When you first type and enter the URL for a route of the application or you refresh the browser tab it hits the server and loads the index.html file rendered on the server-side with the help of renderToString.
ReactDOMServer.renderToString(...)`
Then index.html is viewed and is hydrated (attaching event handlers...etc) to this skeleton HTML file.
Note that you load the index.html from the build folder and replace only the div with root as the id. After building the app it adds js resources need to change the DOM (these resources are actually your frontend logic you wrote for your app) and this is required for client-side rendering. If you check the index.html it has the following script tags to load them.
...
<script src="/static/js/xxxxxxxxxxxxxxx.chunk.js">
...
When you go to another route by clicking a link inside your app. It does not hit the server again and it starts to execute js attached from the client bundle as I said above. Client-side js does the routing of the app properly. That's why it's not hitting your server. This is the expected nature of an isomorphic web application(Behaves the same on the server-side or client-side). And after that, if you refresh the browser that again loads the index.html from the server.

Related

Error: Network Error with React, Express, Axios

I'm trying to run a website on an AWS EC2 instance using React, Express and Axios.
The problem: I'm unable to make axios calls to the Express back-end running on the same instance. The code works fine on my local machine, but on the EC2 instance I get Error: Network Error. The server runs on port 5000 and the client runs on port 3000. I'm able to access the website from the outside using the URL, and am able to make GET requests to the back-end directly using Postman.
Home.js (frontend)
import { useState, useEffect } from 'react';
import axios from 'axios';
function Home() {
const [text, setText] = useState('');
useEffect(() => {
axios.get(`http://172.31.20.10:5000/`)
.then(
result => {
setText(result.data);
},
err => {
alert(err);
setText('An error has occurred');
}
);
});
return (
<h1>{ text }</h1>
);
}
export default Home;
App.js (frontend)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Home from './Home';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<Home />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.js (backend)
const express = require('express');
const cors = require('cors');
const app = express()
const port = 5000;
app.use(express.json());
app.use(cors());
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`App listening on http://localhost:${port}`);
});
172.31.20.10 is the private IP of the EC2 instance. I've tried using localhost instead of the IP, https:// instead of http://, not use http(s) at all, leaving the http and IP/localhost out entirely, etc etc. None have worked so far. Any help would be much appreciated.
To answer the comments: The code did work on my own computer, and the whole network error was a CORS error ("CORS request did not go through" or some such).
I'm still not exactly sure what went wrong, but I "solved" it by hosting the client and server on two different instances. Not sure how or why it works, but it did...

How to configure using Styleguidist with Apollo/GraphQL

I'm trying to use GraphQL to populate fake data for Styleguidist. I'm using Express to make my GraphQL server but I'm unsure how to connect Apollo into Styleguidist? The examples use the index.js file and wrap the root component in an tag for Apollo.
I am unsure how Styleguidist works, I don't know where the index.js file is.
There are ways to configure Styleguidist through webpack, but I don't know how to use webpack to use Apollo.
Each example in Styleguidist is rendered as an independent React tree, and the Wrapper component is the root component, so you need to override it as show in the Redux example like this:
// lib/styleguide/Wrapper.js
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
const client = new ApolloClient({ /* ... */ });
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
// styleguide.config.js
const path = require('path');
module.exports = {
styleguideComponents: {
Wrapper: path.join(__dirname, 'lib/styleguide/Wrapper')
}
};
So you can use Styleguidist in two ways, one by using Create React App then installing an NPM Styleguidist package. Then the other method that I found is starting from an example github registry and replacing the components as you go. I had done the first: where I used Create React App so Webpack was not installed in my main folder but was being used in the NPM module.
With that method I was getting the error:
"Module parse failed: Unexpected token (16:6)
You may need an appropriate loader to handle this file type."
Which means that I needed to configure Webpack. I didn't solve this, but it may just need to have styleguide.config.js file configured to work with Babel. (just a guess)
So, could not (so far), successfully use the Wrapper that solves the problem. So instead I downloaded an example of Styleguidist at https://github.com/styleguidist/example and started fresh. I'm not sure what the difference is, but when I used a wrapper it worked well to add an ApolloProvider wrapper to every component on my page.
To get Apollo 2 to work though you also need to use HttpLink and InMemoryCache. The have a general setup about this at: https://www.apollographql.com/docs/react/basics/setup.html. Under creating a client.
I was using a different port for my GraphQL server because it was using a GraphQL/Express server at port 4000 and Styleguidist by default is at port 6060. So I did two things: passed a uri to the new HttpLink and added a line to the express server to allow cors.
The ref for cors in Express GraphQl and Apollo server:
https://blog.graph.cool/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
So my wrapper file looks like:
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const link = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
and my server file looks like:
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema/schema');
const cors = require('cors');
const app = express();
app.use(cors());
app.use('/graphql', expressGraphQL({
schema: schema
, graphiql: true
}));
app.listen(4000, () => {
console.log('..listening');
});

Receiving "Uncaught Error: Unable to find element with ID 15" when requesting a non-index page of an isomorphic app

The stack I'm working with consists of node, express, webpack, redux, react-router, and react. My express server looks as follows:
import Express from 'express';
import { Server } from 'http';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import App from './src/App';
import routes from './src/routes';
import reducer from './src/reducer';
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'src'));
app.use(Express.static(path.join(__dirname, 'clients/Sample/bundle')));
const store = createStore(
reducer,
{},
applyMiddleware(thunkMiddleware),
);
app.get('*', (req, res) => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App routes={routes} />
</StaticRouter>
</Provider>,
);
return res.render('index', { markup });
});
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, (err) => { // eslint-disable-line consistent-return
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});
The application works perfectly if I attempt to access it directly on http://localhost:3000/. From there, I can navigate to other pages without issues. However, if I try to visit one of those other pages directly in the URL (e.g. http://localhost:3000/user) the console displays the following stack trace:
Uncaught Error: Unable to find element with ID 15.
at invariant (vendor.js:1)
at precacheChildNodes (vendor.js:1)
at Object.getNodeFromInstance (vendor.js:1)
at Object.didPutListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at CallbackQueue.notifyAll (vendor.js:1)
at ReactReconcileTransaction.close (vendor.js:1)
at ReactReconcileTransaction.closeAll (vendor.js:1)
at ReactReconcileTransaction.perform (vendor.js:1)
This then breaks navigation on the site, and redux state changes too. I've identified the element with this ID as being a imported from react-router-dom. I did find GitHub issues describing similar errors (one and two), sadly though the proposed solutions haven't worked for me. Any suggestions on what might be causing this for me?
I found this was actually related to a DOM validation error which was occurring as a result of having react-router-dom's <Link> within react-bootstrap's <NavItem>. In effect, the application was trying to render the following:
<a role="button" href="#" data-reactid="14">
Welcome, User
</a>
Of course, nested anchor tags are invalid markup. See the question here for further detail and some proposed methods of fixing the issue: React-Bootstrap link item in a navitem

React-Router Nested Routes not working

Steps to reproduce
client.js (entry file)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';
import reducers from './reducers';
import routes from './routes.js';
const storeWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = storeWithMiddleware(reducers);
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>, document.getElementById('app')
);
routes.js (ver 1)
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './components/bases/app.js';
import Login from './components/app/authentication/login.js';
export default(
<Route path="/" component={App}>
<Route path="signup" component={Login}/>
</Route>
)
routes.js (ver 2)
let routes = {
path: '/',
component: App,
childRoutes: [
{ path: 'signup', component: Login }
]
}
export default routes;
Expected Behavior
Expect to have /signup route avail.
Actual Behavior
react-router cannot find the route /signup but can find /
Having a look at the chrome dev-tools source-tab, this is what I find:
When looking at "/"
sources
--------
dist/prod
| bundle.js
index.html
When looking at "/signup"
sources
--------
signup
If you changed to hashHistory and it worked it probably be your backend which serves the html...
Since hashHistory works like this:
example.com/#/signup
The browser doesn't understand as a new GET, if you use browseHistory, this:
example.com/signup
Makes the browser request for index.html again but on path /signup ... but the webpack dev server probably don't understand..
Try adding historyApiFallback: true to webpack config
LIke this
https://github.com/amacneil/react-router-webpack-history-example
The giveaway is the files that are being served when you are looking at the sources. When you are trying to load the /signup page, your browser is trying to load a signup page.
When you use browserHistory, you need to serve your index.html (and any scripts included in it) for all possible routes. This means that you need to have a server which accepts all possible routes and responds accordingly.
For example, if you are running a node server using express, you would need to have a wildcard route handler:
// define static handler first for .js, etc.
app.use(express.static(path.join(__dirname, 'public')));
// all other routes should server your index.html file
app.get("/", handleRender);
app.get("*", handleRender);
function handleRender(req, res){
res.sendFile(__dirname + '/index.html');
}

React-Router Webpack exclude server logic from bundling to client side javascript

I am making an isomorphic react application, but now I am stuck of figuring out how to exclude server-side logic from bundling into client side javascript using react-router and webpack.
So my webpack has an entry points to "client.js" which is the clientside bundle javascript.
import React from "react"; import Router from "react-router";
import routes from "../shared/routes";
Router.run(routes, Router.HistoryLocation, (Handler, state) => {
React.render(<Handler/>, document.getElementById('react-app')); });
"client.js" contains react-router routes definition.
And for the server side, I have epxress and route set up as * (all requests route to here)
"server.js"
import routes from "../shared/routes";
app.get('/*', function (req, res) {
Router.run(routes, req.url, (Handler, state) => {
let html = React.renderToString(<Handler/>);
res.render('index', { html: html });
});
});
Since both client and server share the same routes, if I want to set up a route in the react-router e.g. /attractions/:id that will contain server side logic (database query, etc), it will get bundled by the webpack to the client.js
So I am wondering if there is a way to keep just one routes.js that shared by both "client.js" and "server.js" and have "client.js" not bundle some of the server routes.
I came up few possible solutions. But would like to see the best way to do it.
Keep two routes, one for server and one for client, and server routes is the superset of client routes.
Add another layer of abstraction to react-router, so instead of
<Route handler="/attraction/:id"/>
I can use import ABC from "ABCRouteController" and ABCRouteController will determine whether it's node or client and generate route or not generate route.
class AppController extends React.Component {
render () {
let route;
if #isServer
route = <Route handler={#someHandler}" path="/">
else
route =""
return route;
}
}
Add specific routing to server.js. So instead of
app.get('/*', function (req, res) {
Router.run(routes, req.url, (Handler, state) => {
let html = React.renderToString();
res.render('index', { html: html });
});
});
We add more specific routing for handling pure server side logic (similar to two seperate react-router for server and client)

Categories