I've read the documentation, but I didn't really understand the difference between hydrate() and render() in React 16.
I know hydrate() is used to combine SSR and client-side rendering.
Can someone explain what is hydrating and then what is the difference in ReactDOM?
From the ReactDOMServer docs (emphasis mine):
If you call ReactDOM.hydrate() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
The text in bold is the main difference. render may change your node if there is a difference between the initial DOM and the current DOM. hydrate will only attach event handlers.
From the Github issue that introduced hydrate as a separate API:
If this is your initial DOM:
<div id="container">
<div class="spinner">Loading...</div>
</div>
and then call:
ReactDOM.render(
<div class="myapp">
<span>App</span>
</div>,
document.getElementById('container')
)
intending to do a client-side only render (not hydration).
Then you end with
<div id="container">
<div class="spinner">
<span>App</span>
</div>
</div>
Because we don't patch up the attributes.
Just FYI the reason they didn't patch the attributes is
... This would be really slow to hydrate in the normal hydration mode and slow down initial render into a non-SSR tree.
I don't have anything specific to add to what's been said above about the use of hydrate, but in trying to learn about it I put together a little example, so here's the work for whoever finds it helpful.
Goal
Serve two pages, one which uses ReactDOM.hydrate and one which uses ReactDOM.render. They will depend upon some react components written in JSX, which are loaded by <script> tags, given artificial delay (by the server) to illustrate the difference between hydrate and render.
Basic Structure
One file which has the HTML "skeleton"
One file with the custom React components written in JSX
One script which generates all pages for the server to use
One script to run the server
Results
After I generate the pages and run the server, I go to 127.0.0.1 and am presented with the header hydrate, a button, and two links. I can click the button, but nothing happens. After a few moments, the document finishes loading and the button starts counting my clicks. Then I click on the "render" link. Now, the page I'm presented with has the header render and two links, but no button. After a few moments, the button appears and is immediately responsive.
Explanation
On the "hydrate" page, all the markup is immediately rendered, because all the necessary html is served with the page. The button is unresponsive because there are no callbacks connected yet. Once components.js finishes loading, the load event fires from the window and the callbacks are connected with hydrate.
On the "render" page, the button markup isn't served with the page, but only injected by ReactDOM.render, so it isn't immediately visible. Note how the appearance of the page is jarringly changed by the script finally loading.
Source
Here is the custom react component I am using. It will be used by the server in node with react to statically render components, and it will also be loaded dynamically from the server for use in pages (this is the purpose of checking for exports and React objects at the beginning of the file).
// components.jsx
var exports = typeof(exports) == 'object' ? exports : {};
var React = typeof(React) == 'object' ? React : require('react');
function MyButton(props) {
[click, setClick] = React.useState(0);
function handleClick() { setClick(click + 1); }
return (
<button onClick={handleClick}>Clicked: {click}</button>
);
}
exports.MyButton = MyButton;
This is the script used to generate all the pages required for the server. First, babel is used to transpile components.jsx into javascript, then these components are used, along with React and ReactDOMServer, to create the actual pages. These pages are created with the fuction getPage which is exported from the file pageTemplate.js, shown next.
// genScript.js
let babel = require('#babel/core');
let fs = require('fs');
let ReactDOMServer = require('react-dom/server');
let React = require('react');
let pageTemplate = require('./pageTemplate.js');
script = babel.transformFileSync(
'components.jsx',
{presets : [['#babel/react']]}
);
fs.writeFileSync('components.js',script.code);
let components = require('./components.js');
hydrateHTML = pageTemplate.getPage(
'MyButton',
ReactDOMServer.renderToString(React.createElement(components.MyButton)),
'hydrate'
);
renderHTML = pageTemplate.getPage(
'MyButton',
'',
'render'
);
fs.writeFileSync('hydrate.html',hydrateHTML);
fs.writeFileSync('render.html',renderHTML);
This file just exports the getPage function mentioned previously.
// pageTemplate.js
exports.getPage = function(
reactElementTag,
reactElementString,
reactDOMMethod
) {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js" defer></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" defer></script>
<script src="./components.js" defer></script>
</head>
<body>
<h1>${ reactDOMMethod }</h1>
<div id="react-root">${ reactElementString }</div>
hydrate
render
</body>
<script>
window.addEventListener('load', (e) => {
ReactDOM.${ reactDOMMethod }(
React.createElement(${ reactElementTag }),
document.getElementById('react-root')
);
});
</script>
</html>
`;
}
Finally, the actual server
// server.js
let http = require('http');
let fs = require('fs');
let renderPage = fs.readFileSync('render.html');
let hydratePage = fs.readFileSync('hydrate.html');
let componentsSource = fs.readFileSync('components.js');
http.createServer((req, res) => {
if (req.url == '/components.js') {
// artificial delay
setTimeout(() => {
res.setHeader('Content-Type','text/javascript');
res.end(componentsSource);
}, 2000);
} else if (req.url == '/render.html') {
res.end(renderPage);
} else {
res.end(hydratePage);
}
}).listen(80,'127.0.0.1');
Hydrate is basically used in case of SSR(Server side Rendering). SSR gives you the skeleton or HTML markup which is being shipped from a server so that for the first time when your page loads it is not blank and search engine bots can index it for SEO(A use case of SSR). So hydrate adds the JS to your page or a node to which SSR is applied. So that your page responds to the events performed by the user.
Render is used for rendering the component on client side browser Plus if you try to replace the hydrate with render you will get a warning that render is deprecated and can't be used in case of SSR. it was removed because of it being slow as compared to hydrate.
In addition to above...
ReactDOM.hydrate() is same as render(), but it is used to hydrate(attach event listeners) a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Using ReactDOM.render() to hydrate a server-rendered container is deprecated because of slowness and will be removed in React 17 so use hydrate() instead.
The entire process of putting functionality back into the HTML that was already rendered in server side React is called hydration.
So the process of re-rendering over the once rendered HTML is referred to as hydration.
So if we try to hydrate our application by calling ReactDOM.render() its supposed to be done by calling ReactDOM.hydrate().
render will flush out anything in the specified element(named as 'root' in most cases) and rebuild it ,while hydrate will keep anything that is already inside the specified element and build from that,making the initial page load faster.
Related
I've got a React application, in which I need to display an HTML Popup with some dynamic content generated by the server. This content also contains some JSX markup which I want to be rendered into real components.
<MyButton onClick={displayInfo}/>
...
async displayInfo() {
let text = await fetch(...)
console.log(text) // "<SomeComp onClick={foo}><OtherComp..... etc
let component = MAGIC(text)
ReactDom.render(component, '#someDiv')
}
Due to how my server app is structured, ReactServerDOM und hydration is not an option.
Is there a way to implement this on the client side only?
As others mentioned, this has code smell. JSX is an intermediary language intended to be compiled into JavaScript, and it's inefficient to compile it at run time. Also it's generally a bad idea to download and run dynamic executable code. Most would say the better way would be to have components that are driven, not defined, by dynamic data. It should be possible to do this with your use case (even if the logic might be unwieldy), though I'll leave the pros/cons and how to do this to other answers.
Using Babel
But if you trust your dynamically generated code, don't mind the slower user experience, and don't have time/access to rewrite the backend/frontend for a more efficient solution, you can do this with Babel, as mentioned by BENARD Patrick and the linked answer. For this example, we'll use a version of Babel that runs in the client browser called Babel Standalone.
Using Dynamic Modules
There needs to be some way to run the compiled JavaScript. For this example, I'll import it dynamically as a module, but there are other ways. Using eval can be briefer and runs synchronously, but as most know, is generally considered bad practice.
function SomeComponent(props) {
// Simple component as a placeholder; but it can be something more complicated
return React.createElement('div', null, props.name);
}
async function importJSX(input) {
// Since we'll be dynamically importing this; we'll export it as `default`
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
// now we'll create a data url of the compiled code to import it
var dataUrl = 'data:text/javascript;base64,' + btoa(output);
return (await import(dataUrl)).default;
}
(async function () {
var element = await importJSX('<SomeComponent name="Hello World"></SomeComponent>');
var root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
})();
<script src="https://unpkg.com/#babel/standalone#7/babel.min.js"></script>
<script src="https://unpkg.com/react#18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#18/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Adding a Web Worker
For a better user experience, you'd probably want to move the compilation to a web worker. For example:
worker.js
importScripts('https://unpkg.com/#babel/standalone#7/babel.min.js');
self.addEventListener('message', function ({ data: { input, port }}) {
var moduleInput = 'export default ' + input;
var output = Babel.transform(
moduleInput,
{
presets: [
// `modules: false` creates a module that can be imported
["env", { modules: false }],
"react"
]
}
).code;
port.postMessage({ output });
});
And in your main script:
var worker = new Worker('./worker.js');
async function importJSX(input) {
var compiled = await new Promise((resolve) => {
var channel = new MessageChannel();
channel.port1.onmessage = (e) => resolve(e.data);
worker.postMessage({ input, port: channel.port2 }, [ channel.port2 ]);
});
var dataUrl = 'data:text/javascript;base64,' + btoa(compiled.output);
return (await import(dataUrl)).default;
}
(async function () {
var root = ReactDOM.createRoot(document.getElementById('root'));
var element = await importJSX('<div>Hello World</div>');
root.render(element);
})();
This assumes React and ReactDOM are imported already, and there's a HTML element with id root.
Skipping JSX and Compilation
It's worth mentioning that if you're generating JSX dynamically, it's usually only slightly more complex to instead generate what that JSX would compile to.
For instance:
<SomeComponent onClick={foo}>
<div id="container">Hello World</div>
</SomeComponent>
When compiled for React is something like:
React.createElement(SomeComponent,
{
onClick: foo
},
React.createElement('div',
{
id: 'container'
},
'Hello World'
)
);
(See https://reactjs.org/docs/jsx-in-depth.html for more information on how JSX gets compiled and https://babeljs.io/repl for an interactive website to compile JSX to JS using Babel)
While you could run Babel server-side to do this (which would add additional overhead) or you could find/write a pared-down JSX compiler, you also probably can also just rework the functions that return JSX code to ones that return regular JavaScript code.
This offers significant performance improvements client-side since they wouldn't be downloading and running babel.
To actually then use this native JavaScript code, you can export it by prepending export default to it, e.g.:
export default React.createElement(SomeComponent,
...
);
And then dynamically importing it in your app, with something like:
async function displayInfo() {
let component = (await import(endpointURL)).default;
ReactDom.render(component, '#someDiv');
}
As others have pointed out, Browsers don't understand JSX. Therefore, there's no runtime solution you can apply that will actually work in a production environment.
What you can do instead:
Write a bunch of pre-defined components in the front-end repository. (The JSX with the server).
Write a HOC or even just a component called ComponentBuilder will do.
Your <ComponentBuilder /> is nothing but a switch statement that renders one or more of those pre-defined components from step 1 based on what ever you pass to the switch statement.
Ask the server to send keywords instead of JSX. Use the server sent keyword by passing it to the switch statement. You can see the example below.
Now your server decides what to render. You don't need a runtime solution.
Stick the <ComponentBuilder /> where ever you want to render components decided by the server.
Most importantly, you won't give away a front-end responsibility into the hands of the backend. That's just "very difficult to maintain" code. Especially if you start to bring in styles, state management, API calls, etc.
// Component Builder code
const ComponentBuilder = () => {
const [apiData, setApiData] = useState()
useEffect(() => {
// make api call.
const result = getComponentBuilderDataFromAPI()
if (result.data) {
setApiData(result.data)
}
}, [])
switch (apiData.componentName) {
case 'testimonial_cp':
return <Testimonials someProp={apiData.props.someValue || ''} />
case 'about_us_cp':
return <AboutUs title={apiData.props.title || 'About Us'} />
// as many cases as you'd like
default:
return <Loader />
}
}
This will be a lot more efficient, maintainable and the server ends up deciding which component to render.
Just make sure not to mix up responsibilities. What is to be on the front end must be on the front end.
eg: don't let the server send across HTML or CSS directly. Instead have a bunch of predefined styles on the front-end and let the server just send the className. That way the server is in control of deciding the styles but the styles themselves live on the front-end. Else, it becomes really difficult to make changes and find bugs.
The server sends a bunch of CSS properties. You have a bunch of component styles. There are a bunch of global styles. You also use Tailwind or Bootstrap. You will not know what exactly is setting the style or even who's controlling it. A nightmare to debug and maintain! Saying this from experience.
The above solution should work for you provided you can make the required changes to your architecture! Because it's working in a production environment as I write this answer.
I am trying out remix and ohh boy.. I am stuck on creating a simple counter (clicking button increase count)
I guess I am not supposed to use the useState hook so I tried my luck with loader and action as I believe that this is where it should be handled in Remix
What I have on my component is:
export default function Game() {
const counter = useLoaderData();
return (
<>
<div>{counter}</div>
<div>
<Form method="post">
<button type="submit">click</button>
</Form>
</div>
</>
);
}
Server:
import { ActionFunction, LoaderFunction } from 'remix';
let counter: number = 0;
export const loader: LoaderFunction = async () => {
console.log('game object!', counter);
return counter;
};
export let action: ActionFunction = async ({ request, params }) => {
console.log('[action] game object!', ++counter);
return counter;
};
The code above will have counter always resetting to 0 on every click
Looked around some repositories and what I can find are those storing in Cookie/DB/SessionStorage etc, however what if I just want a simple state for my UI?
You are supposed to use useState for your client-side state in Remix.
If you clone the remix repository and do a grep -r useState in the remix/examples/ folder, you will find many occurrences of it.
For example you have a simple one in the Route modal example (in app/routes/invoices/$id/edit.tsx), being used for a form with controlled inputs.
What Remix does is make it easier to communicate between the client and the server by colocating their codes for the same functionnality, and providing simple ways to perform that communication. This is useful if you need to communicate the data to your server. If it's not the case, then it's totally ok to keep that information only on the client.
About server side rendering
Remix also server-side renders the components by default. Which means that it executes your React code on the server to generate the HTML, and sends that with the JavaScript code. That way the browser can display it even before it executes the JavaScript to run the React code on the browser.
This means that in case your code (or a third party library code you use) uses some browser API in you component, you may need to indicate not to server-side render that component.
There is a Client only components example that demonstrates how to do that. It includes an example with a counter storing its value in the local storage of the browser.
I have a static website made with react that requests data from the backend in the useEffect() hook:
export default const App = () => {
const [data, setData] = useState("");
useEffect(() => {
server.get().then(data => {
setData(data)
})
})
return(
<title>{data}</title>
<h1>{data}</h1>
)
}
However, when Bing crawls the webpage, the following problem occurs:
Bing Screenshot:
<title></title>
<h1></h1>
How can I solve this issue?
React isn't used for static sites. If you'd like to have better SEO and server-side rendering you can use nextjs.
The way your app is setup currently will only return some HTML with and empty body to a GET request to / (which is what I suppose crawlers like the one you mentioned use) and starts rendering components after the JavaScript is loaded.
But if you decide on a server-side rendering approach, whenever a request is made to your app the server will first render the app on it's side and the return an HTML string with the rendered components.
Did you check if your server.get() is returning some data? I can't see any url here, so maybe it's actually returning nothing.
Even so, maybe you forgot to pass the second argument of useEffect, which is an array of arguments, which this hooks uses to trigger itself. For example, if you want to trigger only once, when component is mounted, you need to pass [] as second argument of useEffect.
Following is code to mount shadow dom:
const template = makeTemplate() //helper function for styling etc
const host = document.createElement('custom-element')
const root = host.createShadowRoot()
document.body.appendChild(template)
document.body.appendChild(host)
root.appendChild(document.importNode(template.content, true))
This works, I can test my app's internal function calls by loggings, but nothing appears in dom. My app is made in ReactJS, thus i am able to log ComponentDidMount but cant see app in dom!
Note:
If I refresh the page after initial entry, then this exact process runs to mount, but extention load as expected. Only on first entry there is a issue!
I'm currently reading a book about React and Universal apps in which the author claims that the following is best practice to pass initial state from server to client:
server.js
import React from 'react';
import {renderToStaticMarkup} from 'react-dom/server';
import Myapp from '../MyApp';
import api from '../services';
function renderPage(html, initialData) {
return `
<html>
<body>
${html}
</body>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialData)};
</script>
<script src="bundle.js"></script>
</html>
`;
}
export default function(request, reply) {
const initialData = api.getData();
const html = renderToStaticMarkup(<MyApp />);
reply(renderPage(html, initialData);
}
And then, in the client you would read out the data like this:
bundle.js
const initialData = window.__INITIAL_STATE__ || {};
const mountNode = document.getElementById('root');
ReactDOM.render(<MyApp />, mountNode);
From what I understand is that the initial state first gets converted to a string and then attached as a global object literal to the window object.
This solution looks very rough to me. The book was released in mid 2016. Is usage of window.__INITIAL_STATE__ still the way how to do this or are there better solutions?
For example, I could imagine that it would be possible to offer the initial state in a separate micro service call which then could also be cached better than if the data is embedded directly into the document because then the initial state data has to be transferred every time the page refreshes, even if the data hasn't changed.
Simple answer: Yes.
But I'm not sure why no one has pointed out that you have a very common XSS vulnerability using JSON.stringify(initialData) what you want to do instead is to:
import serialize from 'serialize-javascript';
window.__INITIAL_STATE__ = ${serialize(initialData)};
HTTP works by caching responses, in your case, if the initial state will always be the same, you can also cache this in server side and display it in the page, it will work faster, because react will have access immediately to this value so it will not have to wait. Also you can also force the browser to cache the page, so the response for the page will be the same with the initial state not changing.
With the extra call request, you rely on the browser to cache that call, but you'll have to build an extra step, make react re-render when the information arrives or block react to render until the information is ready.
So I'll go with number 1, gives you more flexibility and some other nice to have, like server rendering, which can be easily achieved after having the state loaded in the server.