I am trying to migrate my server side rendering logic to react router 4.But it seems like there many properties are missing like renderProps.
server.js with Old react-router
app.get('/*', (req, res,next) => {
// Server Side Rendering Starts
match({routes:routes(),location:req.url},(err,redirectLocation,renderProps) => {
if (err) return next(err);
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search)
}
if (!renderProps) {
return next(new Error('Missing render props'))
}
const components = renderProps.components
const Comp = components[components.length - 1].WrappedComponent;
const fetchData = (Comp && Comp.fetchData) || (() => Promise.resolve())
const initialState = {}
const store = createStore(reducer, initialState, applyMiddleware(thunk));
const { location, params, history } = renderProps
fetchData({ store, location, params, history }).then(() => {
const body = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
)
const state = store.getState();
// console.log(state)
let head = Helmet.rewind();
res.send(`<!DOCTYPE html>
<html>
<head>
${head.title}
${head.meta}
${head.link}
</head>
<body>
<div id="app" >${body}</div>
<script>window.__STATE__=${JSON.stringify(state)}</script>
<script src="/bundle.js"></script>
</body>
</html>`)
})
.catch((err) => next(err))
})
});
NewServer.js
app.get('*',(req,res) => {
// res.sendFile(path.join(__dirname,'./index.html'));
const routerContext = {};
const components = (
<StaticRouter location={req.url} context={routerContext}>
<Provider store={store}>
<CoolApp/>
</Provider>
</StaticRouter>
)
//I don't Knnow
});
I don't know how to proceed without renderPorps
This is my Sample Component
import React,{Component} from 'react';
import {Helmet} from 'react-helmet';
import {connect} from 'react-redux';
import * as allPosts from '../Redux/Actions/AllPosts_action'
class Home extends Component{
renderAllPosts(){
const {posts} = this.props;
return(
posts.postArr.map(({id,title,body}) => {
return <li key={id}>{title}</li>
})
)
}
render(){
console.log(this.props);
return(
<div>
<Helmet>
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="stylesheet" type="text/css" href="/static/css/Home.css" />
</Helmet>
{this.renderAllPosts()}
</div>
)
}
}
Home.fetchData = ({store,location, params, history}) => store.dispatch(allPosts.getAllPosts());
const mapStateToProps = (state) => {
return{
posts:state.allPosts.toJS()
}
};
const mapDispatchToProps = (dispatch) => {
return {
getAllPosts:() => dispatch(allPosts.getAllPosts())
}
};
export default connect(mapStateToProps,mapDispatchToProps)(Home)
This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux. Thanks!
@timdorr, it's really stupid! Many of us have no idea how to use in with server side rendering. Your code from docs doesn't work! DOESN'T WORK! Please, give some boilerplate example of server side rendering and react-router 4! Thanks.
@daryn-k what is wrong with server-side example? I'm using it right now and everything is fine
@iNikNik how do you use it? Code from docs doesn't work. A lot of confusing and incomprehensible things.
@daryn-k Exactly like that:
const context = {}
const markup = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// we're good, send the response
}
@iNikNik it's so stupid! stupid! stupid! IT DOESN'T WORK! Understand? Where is store? Where are routes? I hate all this stuff!
@iNikNik I've got "Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in."
But I exported it - export default withRouter(Root);
@iNikNik Okay, I figured out the problem. It's all because of bootstrap. Just removed it and it's ok now.
@daryn-k what's your solution to fetch data in the server side by react-router@v4?
@rdshoep I didn't solve this problem yet
@daryn-k I find out the way they provided. react-router-config
@rdshoep it is not really that way... All you need to do is create routes config like this:
const routes = [
{ path: '/items', load: () => Promise }
]
Important: There is no need to use this config to render your app! You can use Route components from react-router.v4 to render some stuff. This config describes which data needs to be provided to render components for this path!
Then, on a server side you can just use matchPath:
const promises = routes
// try to mach route and save match info
.map(route => ({ match: matchPath(req.url, route), route }))
// filter matched routes
.filter(matched => !!matched.match)
// load data
.map(matched => matched.route.load(/* some url params from matched.match here*/)
Promise.all(promises).then(dataArray=> {
// do whatever you need with loaded data and then do server render
const context = {}
const markup = ReactDOMServer.renderToString(
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
)
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// we're good, send the response
}
})
And yeah - that is exactly that docs says - https://reacttraining.com/react-router/web/guides/server-rendering/data-loading
@iNikNik yeah. I think the 'static router' mode to define load data function break app structure.
function routerMatch(cxt) {
const req = cxt.request;
const branch = matchRoutes(routes, req.url);
let params = {};
//TODO split params for each request
let components = branch.map(({route, match}) => {
Object.assign(params, match.params);
return route.component;
}
);
return {
components
, params
, query: req.query
}
}
Then through the components array to fetch data before server rendering.
//fetchData.js
//use the component.need node to fetch data.
export function fetchComponentData(store, components, params = {}, query = {}, headers = {}) {
const needs = components.reduce((prev, current) => {
return (current.need || [])
.concat((current.WrappedComponent && (current.WrappedComponent.need !== current.need) ? current.WrappedComponent.need : []) || [])
.concat(prev);
}, []);
return sequence(needs, need => store.dispatch(need({
params
, query
, headers
, store: store.getState()
})));
}
const store = configureStore()
, headers = {'cookie': cxt.get('Cookie')};
fetchData(store, components, params, query, headers)
.then(() => {
let context = {};
const initialView = renderToString(
<Provider store={store}>
<StaticRouter context={context} location={cxt.req.url}>
<App routes={routes}/>
</StaticRouter>
</Provider>
);
if (context.url) {
// Somewhere a `<Redirect>` was rendered
redirect(301, context.url)
} else {
// we're good, send the response
}
});
I think this way is better.
Most helpful comment
@iNikNik it's so stupid! stupid! stupid! IT DOESN'T WORK! Understand? Where is store? Where are routes? I hate all this stuff!