I'm working on a React.js app that must provide keyboard navigation on a horizontal scrolling carousel of items. On the current version, only left and right arrows are used to navigation and enter to make the selection. I'm mounting the listener on my container as:
const App = React.createClass({
componentWillMount() {
document.addEventListener("keydown", this.__onKeyDown);
},
__onKeyDown(event){
...
},
render: function() {
const items = []
Array(10).fill().map((_, i) => items.push(<MovieItem />
return (
<div className="scroller">
{items}
</div>
)
}
});
The above code works as expected until I tried to make it server side. I added the following route on my server config file and got: ReferenceError document is not defined
import { Server } from 'http';
import express from 'express';
import routes from '../routes';
var app = express();
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
let markup;
if (renderProps) {
markup = renderToString(
<Provider store={store}>
<RouterContext {...renderProps}/>
</Provider>
);
}
return res.render('index', { markup });
}
);
});
It is clear to me why the error is happening, and that document is not available on the server. But what is the proper way to deal with it?
I have already tried to add a tabindex to the div that wraps the carousel and listen to div's onkeydown, but it just works when the div is focused.
Instead of adding event listener in componentWillMount, Use componentDidMount which executes only on client.
componentDidMount
Related
I am developing a React.js-Express.js website, and I had set up some basic code with the help of an online example. I had Express.js send an array to the frontend to display it after parsing. However, when I changed the array just a little - literally changed a string to another string - my frontend did not update.
Express - users.js file
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
//res.send('respond with a resource');
res.json([{
id: 1,
username: "bye" //I changed this string (used to be "samsepi0l")
}, {
id: 2,
username: "hi" //And this string (used to be "D0loresH4ze")
}]);
});
module.exports = router;
React - About.js file
import React from 'react'
// import { Link } from 'react-router-dom'
import { Header } from './Header'
export class About extends React.Component {
state = {users: []}
componentDidMount() {
fetch('/users')
.then(res => res.json())
.then(users => this.setState({ users }));
}
render() {
return (
<div>
<Header />
<h2 id="other_pages_h2">About</h2>
<div>
<h1>Users</h1>
{this.state.users.map(user =>
<div key={user.id} style={{color: 'white'}}>{user.username}</div>
)}
</div>
</div>
)
}
}
For some reason, the /about page still displays "samsepi0l" and "D0loresH4ze". How can I fix this?
This is likely caused by the infamous cache. Usually just hitting Crtl + F5 will fix this, if that doesn't work, clear your browser history.
If you're still running into the same problem, then you didn't save the file and/or restart the server since the code change.
I created a project with React, react-router, #loadable/component.
Now I'm trying to add SSR to this project.
I did server side rendering with react-router.
And then I added #loadable/component to import all pages component:
import loadable from '#loadable/component';
const routersConfig = [
{
path: '/',
component: loadable(() => import('./Home')),
exact: true,
},
{
path: '/2',
component: loadable(() => import('./Home2')),
exact: true,
},
];
Then I added all this parts of code: https://www.smooth-code.com/open-source/loadable-components/docs/server-side-rendering/
And now it works.
But It works with the problem: a content blinks while loading.
How I understand the page's loading process:
Browser gets a content generated by SSR (the first query in network tab)
Browser renders a content (with left and top margins )
Browser downloads two enterpoints and vendors from html (app.js, normalizer.js, vendor.js)
Browser executes app.js and normalizer.js. Left and top margins are removed.
App.js starts downloading page's chunk - home.js. In this moment content disappears
When home.js is downloaded, the content appears again.
I shoot a video to illustrate this process. (I'm sorry for quality, stackoverflow forbides files which size is more then 2MB ). I'm throttling network speed to imagine all page's download process.
My question is why the content disappears? How to fix it?
My code
server.js
const sheetStyledComponents = new ServerStyleSheet();
const sheetsJssRegistry = createSheetsRegistry();
const statsFile = path.resolve(process.cwd(), './build-ssr/dist/loadable-stats.json');
const extractor = new ChunkExtractor({
statsFile,
entrypoints: [
'app',
'normalize',
],
});
try {
const client = ApolloSSRClient();
const tree = (
<ApolloProvider client={client}>
<ApplyTheme sheetsRegistry={sheetsJssRegistry}>
<StaticRouter location={req.url}>
<Home />
</StaticRouter>
</ApplyTheme>
</ApolloProvider>
);
// there is combination of Apollo graphql, jss, styledComponent functions
const body = await getMarkupFromTree({
renderFunction: flow(
sheetStyledComponents.collectStyles.bind(sheetStyledComponents),
extractor.collectChunks.bind(extractor),
renderToString
),
tree,
});
const scriptTags = extractor.getScriptTags();
// It isn't used yet
const linkTags = extractor.getLinkTags();
const styleTags = sheetStyledComponents.getStyleTags();
const html = (await rawHtml)
.replace(
'</head>',
`
${styleTags}
<style type="text/css" id='jss-server-side-styles'>
${sheetsJssRegistry.toString()}
</style>
<script>
window.__APOLLO_STATE__ = ${JSON.stringify(client.extract())};
</script>
${scriptTags}
</head>
`
)
.replace('<div id="app"></div>', `<div id="app">${body}</div>`);
res.send(html);
index.jsx
const SSRApp = (
<ApolloProvider client={ApolloClient}>
<ApplyTheme>
<BrowserRouter>
<App />
</BrowserRouter>
</ApplyTheme>
</ApolloProvider>
);
loadableReady(() => (
ReactDOM.hydrate(
SSRApp,
document.getElementById('app'),
)
));
It was my fault.
The hydration version of app contained BrowserRouter -> Switch -> Router -> HomePage
And the SSR version contained only StaticRouter -> HomePage
Because of this, after rendering SSR version, react removed all DOM and created new one with Router.
i changed in server.js. its worked for me
yours maybe (server/index.js or server.js or server/app.js..etc)
import Express from 'express';
import Loadable from 'react-loadable';
// from //
app.listen(3000, () => {
console.log('app now listening on port', port);
});
// to //
import Loadable from 'react-loadable';
Loadable.preloadAll().then(() => {
app.listen(port, () => {
console.log('app now listening on port', port);
});
});
for more config understanding you can see
The first step to rendering the correct content from the server is to make sure that all of your loadable components are already loaded when you go to render them.
To do this, you can use the Loadable.preloadAll method. It returns a promise that will resolve when all your loadable components are ready.
From my express route I'm trying to pass a component to use in a render function, that handles SSR.
Express Route:
import SettingsConnected from '../../../client/components/settings/settings-connected';
function accountTab(req, res, next) {
sendToRenderApp(req, res, { profileInfo }, url, SettingsConnected);
}
Render helper:
export const sendToRenderApp = (req, res, storeObj = {}, urlPath, componentFunc) => {
const store = configureStore(storeObj);
const dynamicComponent = componentFunc;
const component = (
<Provider store={store}>
<React.Fragment>
<dynamicComponent />
</React.Fragment>
</Provider>
);
const sheet = new ServerStyleSheet();
const app = renderToString(sheet.collectStyles(component));
Error:
"Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it."
Things I've already had a look at include this answer below, but I'm not sure how to wrap such a function inside (what I presume) the Provider component?
Functions are not valid as a React child. This may happen if you return a Component instead of from render
ANSWERING OWN QUESTION:
Turned out to be a problem with the client's hydrate. This piece of code above was fine all along - :facepalm:
I'm building isomorphic application using ReactJS with react-router module for routing purposes on server side.
From its guide about using react-router on server:
(req, res) => {
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
//...
else if (renderProps) {
res.status(200).send(renderToString(<RoutingContext {...renderProps} />))
}
//...
})
}
There is almost no information about this RoutingContext. So it's a bit unclear for me how it works. Is it some kind of replacement for Router component from react-router (used on top of other routes)?
Any help in understanding will be really appreciated!
React router v4
in the new version (v4) it has been updated to createServerRenderContext. This works in very different way than previously but is much more concise as it also get rid of the need for using 'match'.
this code example is to be applied as express middleware:
import React from 'react';
import { renderToString } from 'react-dom/server';
import { ServerRouter/* , createServerRenderContext */ } from 'react-router';
// todo : remove line when this PR is live
// https://github.com/ReactTraining/react-router/pull/3820
import createServerRenderContext from 'react-router/createServerRenderContext';
import { makeRoutes } from '../../app/routes';
const createMarkup = (req, context) => renderToString(
<ServerRouter location={req.url} context={context} >
{makeRoutes()}
</ServerRouter>
);
const setRouterContext = (req, res, next) => {
const context = createServerRenderContext();
const markup = createMarkup(req, context);
const result = context.getResult();
if (result.redirect) {
res.redirect(301, result.redirect.pathname + result.redirect.search);
} else {
res.status(result.missed ? 404 : 200);
res.routerContext = (result.missed) ? createMarkup(req, context) : markup;
next();
}
};
export default setRouterContext;
react-lego is an example app that shows how to do universal rendering using createServerRenderContext
RoutingContext is an undocumented feature and will be replaced by RouterContext in v2.0.0. Its role is to synchronously render the route component.
It is simply a wrapper around your component which inject context properties such as history, location and params.
React router v4
in the new version (v4) it has been deleted to createServerRenderContext. This works in very different way than previously but is much more concise.
this little code example is to be applied.
import { StaticRouter } from'react-router-dom'
const context = {}
const mockup = renderToString(
<Provider store = {store}>
<IntlProvider locale = {locale} messages = {messages[locale]}>
<StaticRouter location={request.url} context={context}>
<ModuleReactWithPages />
</StaticRouter>
</IntlProvider>
</Provider>
)
Now it's a layer of itself when it's a 404
I'm following the example reported in the react-router guide
var App = React.createClass({
render: function () {
return <div>Hi</div>;
}
});
var routes = (
<Route handler={App} path="/" />
);
// if using express it might look like this
app.use(function (req, res) {
// pass in `req.url` and the router will immediately match
Router.run(routes, req.url, function (Handler) {
var content = React.renderToString(<Handler/>);
res.render('main', {content: content});
});
});
Quite simple, isn't it? Instead this is my code:
export function index(req: express.Request, res: express.Response, next: Function) {
Router.run(routes, req.url, (Handler, state) => {
fs.readFileAsync(
path.join(__dirname, '..', 'mockData/airport-codes.csv'),
'utf-8'
).then((content) => {
return csv.parseAsync(content);
}).then((parsedContent: Array<string[]>[]) => {
ResponseHelper.renderTemplate('index', res, {
output: React.renderToString(React.createElement(Handler, {
header: [
"ID", "Type", "Name", "Latitude (deg)", "Longitude (deg)", "Elevation", "Continent", "Country ISO", "Region ISO", "Municipality", "GPS Code", "IATA Code", "Local Code"
],
initialData: parsedContent
}))
});
}).catch(next);
});
}
Basically what I do is get data and pass to the Handler to initialise the component. This is the file route in react:
import { Route } from "react-router";
import * as React from "react";
import Excel from "../components/Excel";
export default (
<Route handler={Excel} path="/" name="excel" />
);
and this is the entrypoint in the frontend
import * as React from "react";
import * as Router from "react-router";
import routes from "./shared/routes";
Router.run(routes, Router.HistoryLocation, (Root, state) => {
React.render(<Root />, document.getElementById('app'));
});
My problem is that when I start the application I get this warning:
Warning: Failed propType: Required prop `header` was not specified in `Excel`. Check the render method of `Router`.
Warning: Failed propType: Required prop `initialData` was not specified in `Excel`. Check the render method of `Router`.
and consequently an error. Do you know which is the right way to pass props in this case?
EDIT
error message
Uncaught SyntaxError: Unexpected token &
1../components/Excel # bundle.js:4s # bundle.js:1e # bundle.js:1(anonymous function) # bundle.js:1
The lifecycle of the isomorphic app like yours is that first, the server runs the JS code and return an HTML string expression of the rendered result. It mounts that to the DOM and then loads your frontend app on top of that. Of course the DOM should be identical so it doesn't need to really rerender anything, but it does mount all of the JS event handlers, React code, etc to the window.
It will still process the Router.run method and match the routes, but it will be the same page that was loaded from the server. However, after that point, the front-end has to replicate the server side functionality (ie fetching the data, passing to the routed component).
So, in your frontend code you need to fetch the required data that you would normally do on the server, and pass it down to the <Root /> component as props.
Check out this demo, specifically the app/server.js (server side render) and app/client.js (client side render). Hope this helps!
React Router Mega Demo