I am new in server side rendering. I am trying to implement it like following but, it shows error.
index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
index.js
import React from 'react';
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import rootReducer from './reducers'
import './index.css'
import Main from './Main'
const preloadedState = window.__PRELOADED_STATE__
delete window.__PRELOADED_STATE__
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk)
);
ReactDOM.hydrate(
<Provider store={store}>
<Main />
</Provider>,
document.getElementById('root')
);
server.js
import express from 'express';
import open from 'open';
import rootReducer from '../src/reducers'
import Main from '../src/Main';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { renderToString } from 'react-dom/server'
var app = express();
const port = 4000;
app.use(express.static('dist'));
app.get('/', (req, res) => {
const store = createStore(rootReducer)
const html = renderToString(
<Provider store={store}>
<Main />
</Provider>
)
const preloadedState = store.getState();
res.send(renderFullPage(html, preloadedState))
});
function renderFullPage(html, preloadedState) {
return `
<!doctype html>
<html>
<body>
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g,'\\u003c')}
</script>
</body>
</html>
`
}
app.listen(port, function (err) {
if (err) {
console.log(err);
} else {
open('http://localhost:' + port);
}
})
When i try to run SSR it shows following error. I don't understand what i am doing wrong?
Warning: Expected server HTML to contain a matching <div> in <div>.
Any help would be greatly appreciated.
Edit:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Content from './components/Content';
import PageNotFound from './components/PageNotFound'
import Movie from './components/Movie';
class Main extends Component {
render() {
return (
<div className="main">
<Router>
<Switch>
<Route exact path='/' component={Content} />
<Route path='/search/:query' component={Content} />
<Route path='/film/:id' component={Movie} />
<Route path='*' component={PageNotFound} />
</Switch>
</Router>
</div>
);
}
}
export default Main;
This is not duplicate of given link as i am using SSR. Then why should i use render instead of hydrate as i am using SSR?
Related
I'm trying to create a e-commerce web app by following an online tutorial on Udemy but I have run into an error that I cannot seem to fix even though I have done everything the tutorial has done and theirs is working fine.
The error I am getting is telling me to Check the render method of Header
Which I have done and it is the same as the working tutorial.
This is my index.js file where the error is:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
And this is my header component
import React from 'react';
import { Link } from 'react-router-dom';
import { ReactComponent as Logo } from '../../assets/crown.svg';
import './header.styles.scss';
const Header = () => (
<div className="header">
<Link className="logo-container" to="/">
<Logo className="logo" />
</Link>
<div className="options">
<Link className="option" to="/shop">SHOP</Link>
<Link className="signin" to="/shop">SIGN IN</Link>
<Link className="option" to="/contact">CONTACT</Link>
</div>
</div>
)
export default Header;
This is my App.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import './App.css';
import HomePage from './pages/homepage/homepage.component';
import ShopPage from './pages/shop/shop.component';
import SignInSignUpPage from './components/sign-in-sign-up/sign-in-sign-up.component';
import Header from './components/header/header.component';
import { auth } from './firebase/firebase.utils';
class App extends React.Component {
constructor() {
super();
this.state = {
currentUser: null
};
}
componentDidMount() {
auth.onAuthStateChanged(user => {
this.setState({ currentUser: user });
});
}
render() {
return (
<div>
<Header />
<Switch>
<Route exact path='/' component={HomePage} />
<Route path='/shop' component={ShopPage} />
<Route path='/signin' component={SignInSignUpPage} />
</Switch>
</div>
);
}
}
export default App;
I can not seem to figure out how to solve this issue.
Got a floating period in here...
<Link className="option". to="/contact">CONTACT</Link>
I have information in the state (true or false) that I want to display if is true this Navbar component, but when I use the hook, I get an error message:
hook error
My code:
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import store, { history } from './reduxStore';
import AppRouterContainer from './pages/AppRouterContainer';
import Feedback from './pages/feedback/Feedback';
import Navbar from './components/Navbar/Navbar';
import { useTypedSelector } from '../src/hooks/useTypedSelector';
const isAuth = useTypedSelector((state) => state.auth.isAuth);
const App = () => (
<BrowserRouter>
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRouterContainer />
{isAuth && (
<Navbar />
)}
<Feedback />
</ConnectedRouter>
</Provider>
</BrowserRouter>
);
export default App;
You need to create a wrapper component to have access to store in your context (I think your useTypedSelector() hook needs that access).
You can use hooks only inside a function, not just inside a module.
Check out this example:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router';
import { useTypedSelector } from '../src/hooks/useTypedSelector';
import Navbar from './components/Navbar/Navbar';
import AppRouterContainer from './pages/AppRouterContainer';
import Feedback from './pages/feedback/Feedback';
import store, { history } from './reduxStore';
const NavbarWrapper = () => {
const isAuth = useTypedSelector((state) => state.auth.isAuth);
if (!isAuth) {
return null;
}
return <Navbar />;
};
const App = () => (
<BrowserRouter>
<Provider store={store}>
<ConnectedRouter history={history}>
<AppRouterContainer />
<NavbarWrapper />
<Feedback />
</ConnectedRouter>
</Provider>
</BrowserRouter>
);
export default App;
Also, I think you should move the NavbarWrapper component to a separate file.
I am using React Router. When i try to enter my interface by localhost:3000/Login; i am getting this error. "Invariant failed: Browser history needs a DOM". I googled everywhere but i couldn't find any solutions. I am sharing my codes, anyone to help??
my server.js file
import React from 'react';
import { StaticRouter } from 'react-router-dom';
import express from 'express';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import serialize from 'serialize-javascript';
import App from '$/containers/app';
import setupStore from '$/state/store';
const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
const server = express();
server
.disable('x-powered-by')
.use(express.static(process.env.RAZZLE_PUBLIC_DIR))
.get('/*', (req, res) => {
// Compile an initial state
const preloadedState = { counter: 0 };
// Create a new Redux store instance
const store = setupStore(preloadedState);
const context = {};
// Render the component to a string
const markup = renderToString(
<Provider store={store}>
<StaticRouter context={context} location={req.url}>
<App />
</StaticRouter>
</Provider>,
);
// Grab the initial state from our Redux store
const finalState = store.getState();
if (context.url) {
res.redirect(context.url);
} else {
res.status(200).send(
`<!doctype html>
<html lang="">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="utf-8" />
<title>Welcome to Razzle</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
${
assets.client.css
? `<link rel="stylesheet" href="${assets.client.css}">`
: ''
}
${
process.env.NODE_ENV === 'production'
? `<script src="${assets.client.js}" defer></script>`
: `<script src="${assets.client.js}" defer crossorigin></script>`
}
</head>
<body>
<div id="root">${markup}</div>
<script>
window.__PRELOADED_STATE__ = ${serialize(finalState)}
</script>
</body>
</html>`,
);
}
});
export default server;
my app.js file
import React from 'react';
import Home from '$/components/pages/home';
import Login from '$/components/pages/login';
import Logout from '$/components/pages/logout';
import '$/css/app.css';
const { Route, Switch, BrowserRouter } = require('react-router-dom');
const App = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
</Switch>
</BrowserRouter>
);
export default App;
my client.js file
import React from 'react';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import setupStore from '$/state/store';
import App from '$/containers/app';
const store = setupStore(window.__PRELOADED_STATE__);
const { BrowserRouter } = require('react-router-dom');
hydrate(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById('root'),
);
if (module.hot) {
module.hot.accept('./containers/app', () => {
hydrate(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
});
}
I have a react server-side rendering application along with node & express js.
Routes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Home from './Home';
import Test from './Test';
export default () => {
return (
<div>
<Route exact path="/" component={Home} />
<Route exact path="/test/:deviceId" component={Test} />
</div>
);
};
index.js
import express from 'express';
import renderer from './helpers/renderer';
const app = express();
app.use(express.static('public')));
app.get('*', (req, res) => {
console.log(req.url);
res.send(renderer(req));
});
app.listen(3090, () => {
console.log('listening at http://localhost:3090');
})
renderer.js
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Routes from '../components/Routes';
export default req => {
const content = renderToString(
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
);
return `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="app">${content}</div>
<script src="bundle.js"></script>
</body>
</html>
`;
};
client.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
ReactDOM.hydrate(
<BrowserRouter>
<Routes />
</BrowserRouter>
, document.getElementById('app'));
Well, I am trying to pass a deviceId along with my route /test/:deviceId,
and my HTML template under renderer.js looks for bundle.js file under /test/build.js instead of /bundle.js
How do I make sure my bundle file always points to the correct location which is under public folder exposed by
app.use(express.static('public'));
if I visit, / route to Home component, it looks for bundle.js under correct folder which is the public folder.
Do let me know if you need any extra information.
Well, finally I have figured out what was changing the path of bundle.js with each out.
if my route is '/', it will look for bundle.js at http://localhost:3090/bundle.js
if the route is '/test/:deviceId', it will look for bundle.js at http://localhost:3090/test/:deviceId/bundle.js (in this scenario it will never find bundle.js)
I fixed it my modifying the html temlate under renderer.js
for the tag I changed path to bundle.js from 'bundle.js' to '/bundle.js'
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import Routes from '../components/Routes';
export default req => {
const content = renderToString(
<StaticRouter location={req.path} context={{}}>
<Routes />
</StaticRouter>
);
return `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="app">${content}</div>
<script src="/bundle.js"></script>
</body>
</html>
`;
};
Now, it will always look for bundle.js under http://localhost:3090/bundle.js
I have an application that uses the same layout for all routes... except one.
One route will be completely different than all others.
So the entire application will have a menu, body, footer, etc.
The one-off route will not have any of that and be a completely separate thing.
How should I set this kinda thing up in a react app? Everything I've ever seen/done always has one main wrapping element that has the routes rendered as children.
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import configureStore from './store'
import App from './components/App'
// import registerServiceWorker from './registerServiceWorker'
import { unregister } from './registerServiceWorker'
const preloadedState = window.__PRELOADED_STATE__ ? window.__PRELOADED_STATE__ : {}
// console.log('window.__PRELOADED_STATE__', window.__PRELOADED_STATE__)
delete window.__PRELOADED_STATE__
const Store = configureStore(preloadedState)
const rootEl = document.getElementById('root')
ReactDOM.hydrate(
<Provider store={Store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
rootEl
)
if(module.hot){
module.hot.accept('./components/App', () => {
ReactDOM.hydrate(
<Provider store={Store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
rootEl
)
})
}
// registerServiceWorker()
unregister()
App.js
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
// Components
import AppHelmet from './AppHelmet'
import Notices from './Notices'
import Header from './Header'
import Body from './Body'
import Footer from './Footer'
// Site state
import { getSiteInfo } from '../store/actions/siteInfo'
import { REACT_APP_SITE_KEY } from '../shared/vars'
// CSS
import '../css/general.css'
class App extends Component {
initialAction() {
this.props.getSiteInfo(REACT_APP_SITE_KEY)
}
componentWillMount() {
// On client and site info has not been fetched yet
if(this.props.siteInfo.site === undefined){
this.initialAction()
}
}
render() {
return (
<div>
<AppHelmet {...this.props} />
<Notices />
<div className="body">
<Header />
<Body />
</div>
<Footer />
</div>
)
}
}
const mapStateToProps = (state) => {
return {
siteInfo: state.siteInfo,
user: state.user
}
}
const mapDispatchToProps = (dispatch) => {
return {
getSiteInfo: (siteKey) => dispatch(getSiteInfo(siteKey))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App))
Body.js
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom'
import routes from '../shared/routes'
class Body extends Component {
render() {
return (
<Switch>
{routes.map((route, i) => <Route key={i} {...route} />)}
</Switch>
)
}
}
export default Body
So, as you can see the index.js entry point will render <App />. <App /> will render the main layout, including <Body />, which renders all routes and content.
Cool.
But seeing as I don't want this one-off to render the <App /> layout, I'm not sure how to set this up from index.js. I'm sure it's simple and I'm just not seeing the answer.
One way to achieve what you want is to listen to the router.
You can add the listener into the components you want to hide.
When the listener detects you're on a view where you do not want the components to show, simply don't render them for that view.