I have server code like this:
var data = {
scripts: scripts,
children:[<Comp1 />, <Comp2 />, <Comp3 />]
};
// keeping smaller for easier example than reality
var markup = '';
markup += '<script type="text/javascript">' +
'var PROPS = {' +
'Layout: ' + JSON.stringify(data) +
'};' +
'</script>';
markup += React.renderToString(
<Layout {...data} ></Layout>
);
So the server renders everything fine. Then no matter what I try, I get all sorts of warnings about how I'm handling the children for serialization and re-use in the browser when I run: React.render(App(window.PROPS.Layout), document.getElementById('content'));
Many of my attempts make React complain that I should be using createFragment. But when I do that, I still get errors that I should be wrapping it.
My goal is to render the Layout component, with several children, and have React in the browser know the same elements are below. Many attempts also yield this error:
Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
(client) d=".1frk89jhyio.1"></div><div data-react
(server) d=".1frk89jhyio.1"><div style="float:lef
My client code is this:
var React = require('react'),
Layout = require('./components/layout');
React.render(<Layout {...PROPS.Layout} />, document.getElementById('content'));
You shouldn't stringify your components like that to serve for your client. You should just render you application again. Things to stringify should only be raw data like {id: 1, name: 'limelights'}.
On the server
React.renderToString(<Layout {...data} />);
and on the client
var data = JSON.parse(window.PROPS.data);
React.render(<Layout {...data} />, document.body);
The point of having an isomorphic application is that the same code runs on both the server and the client.
The answer is that I was doing things the theoretical wrong way.
Having the server render like <Layout {...data} ><Comp1 /></Layout> will work. However when the browser kicks in, it won't have children props and you'll lose Comp1. Then I had tried to pass children through in as props like the example in my question. The real way to do this is simply having the server do <Layout {...data} ></Layout>.
The real key to the answer is that the render method of Layout should be placing the children directly. I can use state (or props, still working out semantic differences) within render to determine if children should be different.
Related
I have a React app which uses react-intl to provide translations and it's working fine for browser DOM rendering. I have a component which deals with async locale/translation based on props it receives, using import() and then once locale + translation data are loaded, <IntlProvieder> is rendered. Like so:
// constructor
import(`react-intl/locale-data/${langCode}`).then((localeData) => {
addLocaleData(localeData.
this.setState({localeDataLoaded: true});
});
import(`../../translations/${locale}.json`).then((translations) => {
this.setState({translations: translations.default});
});
render() {
if (!this.state.translations || !this.state.localeDataLoaded) {
return null;
}
return (
<IntlProvider locale={this.props.locale} messages={this.state.translations} >
{this.props.children}
</IntlProvider>
);
}
However, when it comes to SSR, the setState() calls don't trigger render() thus blocking the rest of the app from rendering.
I'd like to know what others are doing with regards to i18n and server/DOM rendering. Ideally I'm not going to be sending extraneous locale data to users over the web and I'd like a single component to manage all of this for both renderers.
Alternatively I may end up baking translations into the output file and generating JS files per locale
For SSR you'd have to load things before rendering in the request lifecycle, then pass those down to react-intl
I'm trying to do SSR with ReactDOMServer.renderToNodeStream(element) but just wanted to know if there would be any problem with using both ReactDOMServer.renderToString(element) and ReactDOMServer.renderToNodeStream(element) at each request?
What I have in my custom SSR setup is:
* React 16
* react-loadable
* styled-components v4
* react-helmet-async
* Redux
* Express JS
Previously with React, I could easily render a HTML document by first rendering the <head></head> tags that contains markup produced by react-helmet and then using ReactDOMServer.renderToString() to render my React elements.
However, by switching to ReactDOMServer.renderToNodeStream() I had to switch react-helmet for react-helmet-async, which supports renderToNodeStream() function. But then when I try to render the <head></head> tags with the markup by react-helmet-async it'll come back as undefined.
To get around this problem, I've had to use renderToString() first without actually writing that out to Express JS response. That way react-helmet-async can then see what meta tags to render and then proceed to use renderToNodeStream and stream that out to the response.
I've simplified my code as much as possible as I want to understand if this would have a negative impact (for performance, or if anyone can think of anything else)?
Before:
let html = ReactDOMServer.renderToString(stylesheet.collectStyles(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<LocalStoreProvider store={store}>
<HelmetProvider context={helmetContext}>
<RouterContext {...renderProps} />
</HelmetProvider>
</LocalStoreProvider>
</Loadable.Capture>
));
const { helmet } = helmetContext;
response.write(
renderDocumentHead({
css: stylesheet.getStyleTags(),
title: helmet.title.toString(),
link: helmet.link.toString(),
meta: helmet.meta.toString()
})
);
response.write(html);
After:
let html = stylesheet.collectStyles(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<LocalStoreProvider store={store}>
<HelmetProvider context={helmetContext}>
<RouterContext {...renderProps} />
</HelmetProvider>
</LocalStoreProvider>
</Loadable.Capture>
);
// do a first pass render so that react-helmet-async
// can see what meta tags to render
ReactDOMServer.renderToString(html);
const { helmet } = helmetContext;
response.write(
renderDocumentHead({
css: stylesheet.getStyleTags(),
title: helmet.title.toString(),
link: helmet.link.toString(),
meta: helmet.meta.toString()
})
);
const stream = stylesheet.interleaveWithNodeStream(
ReactDOMServer.renderToNodeStream(html)
);
// and then actually stream the react elements out
stream.pipe(response, { end: false });
stream.on('end', () => response.end('</body></html>'));
Unfortunately, the only way I could get react-helmet-async to work correctly, I have to do this two-pass render. My CSS styles, etc. resolves correctly and the client renders/hydrates correctly too. I've seen other examples where react-apollo was used and the getDataFromTree data rehydration method was used which allows react-helmet-async to see what was needed to render the head markup. But hopefully there are no issues with my two-pass rendering approach?
Imagine you made a web framework that helps you quickly make blogs for clients. For the sake of this post, its the same blog template everytime, what changes is the content. You're React app is a simple structure of the following [where the Content state is just changing each time]
<App>
<Navigation/>
<Content/>
</App>
What makes the framework is you have XML files which contain the HTML. Each XML file represents one blog post. The app pulls all the HTML from the XML files, and puts it into the state of the App in a "blog posts" array. Depending on the state of the app, a specific entry in the array will be displayed in Content...
Content's state has a field called "html" which is what holds the HTML to be injected in string form. [you have to use dangerouslySetInnerHTML]
This concept works fine, and I have a version of it now. However, imagine you have a React components that you want to add to each blog post. Say you want to add the component into a specific blog post in a specific section. You want to add props to it and such. Now this goes out the window with dangerouslySetInnerHTML
This is where I am stuck trying to find the best direction to go. The only thought I have now is the following:
Since you would now be writing JSX in the XML, just make each blog post its own component. You would have ...etc and then if this.state.currentPost === 1 then display BlogPost1 and likewise. Yet you would have to have a huge block of if-statements depending on how many blogposts you have, and its not ideal to have to add everytime you have a new blogpost
When I read the title of your question I got curious and found this library to parse XML into React components: xml-to-react. But that's not what you are asking for.
As you want to use components in the middle of you string of HTML, I'll suggest: react-remarkable. This component compiles its children (a string with markdown/html/react) into react nodes.
Example from its docs:
var React = require('react');
var Markdown = require('react-remarkable');
var MyComponent = React.createClass({
render() {
return (
<div>
{/* Pass Markdown source to the `source` prop */}
<Markdown source="**Markdown is awesome!**" />
{/* Or pass it as children */}
{/* You can nest React components, too */}
<Markdown>{`
## Reasons React is great
1. Server-side rendering
2. This totally works:
<SomeOtherAmazingComponent />
Pretty neat!
`}</Markdown>
</div>
);
}
});
I'm fairly new to React to trying to wrap my head around routing via React Router while also passing required data to components. I will probably eventually incorporate Redux in my app, but I'm trying to avoid it initially.
It seems like using React Router as opposed to serving individual pages from the server means having to store state data in the App.js component since that's where the Router exists.
For example if I'm on site.com/x and I want to navigate to site.com/y and /x looks like this:
<div>
<XOuter >
<XInner />
</XOuter>
</div>
And App.js looks like this:
<BrowserRouter>
<Route exact path="/x" component={X} />
<Route exact path="/y" component={Y} />
</BrowserRouter>
... if the GET request is being called from XInner and the results will inform the content of /y, XInner will have to pass the response all the way back to App.js to properly render /y.
It seems like this could get messy quickly. Is there any way to avoid it?
This isn't as bad as you think, for two reasons:
If you use React Router's <Link> component to create links instead of using <a> directly, it will add event handlers that cancel the link's actual navigation and instead use history.pushState to do the navigation. To the user, they think they're on the new page (the URL bar shows this), but no GET request ever actually happened to load it.
React Router's paths are parsed via path-to-regexp. This lets you add parameters to the URL and then extract them from the router's props. You can also put data in the query string and then parse it later. This will let you pass state from one page to another without using any top-level React state, with the added benefit of making the browser's history and URL copying automatically work right.
The data is stored in the path instead of App.js. Path should be converted to props through pure function so the same path is always converted to the same props. That's the external state that chooses a <Route /> and sets its props.
Root of your problems lies in this design:
if the GET request is being called from XInner and the results will inform the content of /y, XInner will have to pass the response all the way back to App.js to properly render /y
Remove the if the GET request is being called from XInner... and all your concerns become moot.
Component A should not be responsible for fetching data for Component B. If /y needs data, fetch the data in Y's componentdidmount.
Example code showing the concept
fetchData(){
fetch(...) // or axios or whatever
.then(() => {
this.setState({
data: 'Hello World'
})
})
}
componentDidMount(){
this.fetchData()
}
render() {
return(
<div>
{this.state.data}
</div>
)
}
In my serverside react rendering, I pass a property to the JSX:
markup: React.renderToString(Admin({ field1: "Hallo" }))
The JSX looks like this:
<MaterialTextField hintText="field1" floatingLabelText="field1" type="text" name="field1" value={this.props.field1} />
Now, I ned to render the JSX also on clientside for having the event listeners, etc.:
React.render(
<Admin />,
document.getElementById('react-app')
);
The problem is: Because the rendered markups are not the same, the value of the text-field gets lost. How could I fix that?
React will check that any initial markup present matches what's produced for the first render on the client by comparing checksums between the initial client render and a checksum attribute in the server-rendered markup, so you must make the same props available for the initial render on the client in order to reuse the markup.
A common way to do this is to serialise the props to JSON so they can easily be included as a variable in the initial HTML sent to the client:
res.render('react.jade', {
markup: React.renderToString(React.createElement(MyComponent, props)),
props: JSON.stringify(props)
})
...
body
div#app
!= markup
script window.INITIAL_PROPS = !{props}
script(src='/js/app.js')
By passing it as prop to the Admin component just as you pass this.props.field1 to the MaterialTextfield using <Admin field1="Hallo" />