I'm trying to render react isomorphicaly, and it renders but I get the Warning/Error in client saying:
I'm using jspm and npm as package managers;
warning.js:25 Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.
The source outputed by server renderToString:
<div id="reactRoot"><div data-reactroot="" data-reactid="1" data-react-checksum="845161664"><marquee data-reactid="2"><h1 data-reactid="3">App</h1></marquee></div></div>
The source replaced by react after render:
<div id="reactRoot"><div data-reactid=".0"><marquee data-reactid=".0.0"><h1 data-reactid=".0.0.0">App</h1></marquee></div></div>
The express server middleware:
import App from './components/App/App.jsx';
// [...] Express code removed for brevity
app.use('*', (req, res, next) => {
try {
res.render('index.html', {
reactHtml: renderToString(
<App />
)
});
} catch (err) {
next(err);
}
});
The index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>App</title>
<script>
console.log('Running on <%- #env %> environment!');
<% if (#env == 'development') : %>
System.import('systemjs-hot-reloader/default-listener.js');
<% end %>
System.import('/app.jsx!')
.catch(console.error.bind(console));
</script>
</head>
<body>
<div id="reactRoot"><%- reactHtml %></div>
</body>
</html>
I'm using ect as templating engine here;
The app.jsx:
import { render } from 'react-dom';
import App from './components/App/App.jsx';
const mountPoint = document.getElementById('reactRoot');
render(
<App />,
mountPoint
);
The App/App.jsx:
import React from 'react';
const App = () => (
<div>
<marquee><h1>App</h1></marquee>
</div>
);
export default App;
Use renderToStaticMarkup
// render the dynamic code of the page to a string.
var appHtml = React.renderToString(<Application/>);
// now use React as a static templating language to create the <html>,
// <head>, and <body> tags
var fullPageHtml = React.renderToStaticMarkup(<HtmlPage content={appHtml}/>);
res.write(fullPageHtml);
Full Issue resolution discussion can be found here.
https://github.com/aickin/react-dom-stream/issues/4
Related
I need to hydrate a root element without actually importing the component in my client side javascript. I am rendering the component server-side, where each route will render a different full-page react component.
Here is my server.js:
import express from 'express'
import path from "path"
import React from "react"
import ReactDOMServer from "react-dom/server"
import About from "./about"
import Home from "./home"
const app = express()
const port = 3000
app.get('/', (req, res) => {
render(res, <Home />)
})
app.get('/about', (req, res) => {
render(res, <About />)
})
function render(res, component) {
res.send(`
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>SSR App</title>
</head>
<body>
<div id="root">${ReactDOMServer.renderToString(component)}</div>
<script src="client.js"></script>
</body>
</html>
`)
}
app.use(
express.static(path.resolve(__dirname, "..", "public"))
)
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
You can see I am importing the Home and About components for each of their respective routes. These are full page react components.
Here is what home.js looks like, for example:
import React from "react"
export default function Home() {
const [times, setTimes] = React.useState(0)
return (
<div>
<h1>Home</h1>
<p>clicked {times} times</p>
<button onClick={() => setTimes((times) => times + 1)}>add</button>
</div>
)
}
Now here is the part I need to change. I have a client-side script, and right now, I am importing the Home component there. However, obviously this doesn't work when I visit the /about route, because I don't have hydration code for that particular component. I basically need to dynamically hydrate the root element based on the server-side route rendered html.
Example client.js:
import React from "react"
import ReactDOM from "react-dom/client"
import Home from "./home"
ReactDOM.hydrateRoot(document.getElementById("root"), <Home />)
What I need it to look like:
import React from "react"
import ReactDOM from "react-dom/client"
ReactDOM.hydrateRootDynamically(document.getElementById("root"))
Here are the scripts I am using:
"scripts": {
"client": "esbuild src/client.js --bundle --outfile=public/client.js --loader:.js=jsx",
"server": "esbuild src/server.js --bundle --outfile=public/server.js --loader:.js=jsx --platform=node",
"dev": "npm run client && npm run server && node public/server.js"
},
Like I said this currently works for the / home page, but I want to add more express routes that point to different react full-page components, and I don't want to have to create client-side hydration scripts for every single route.
How do I do this?
Created a react app and then converted it into a single spa react app using
https://www.youtube.com/watch?v=W8oaySHuj3Y
When a hit is made to http://localhost:8080/org-app.js I get a response of the javascript files.
Also when http://single-spa-playground.org/playground/instant-test?name=#org/app&url=8080 the app loads.
However now trying to import the same app in an html page does not replaces the tag.However, it loads the component know this because of the api calls being made and redux store being loaded.
Have not done singleSpa.registerApplication even if I do it is it necessary a root component needs to be made to register the application.
org-app.js
import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";
import il8n from "./i18n";
const domElementGetter = () => {
let el = document.getElementById("example-app");
if (!el) {
el = document.createElement("div");
el.id = "example-app";
document.body.appendChild(el);
}
return el;
};
const lifecycles = singleSpaReact({
React,
ReactDOM,
il8n,
rootComponent: Root,
errorBoundary(err, info, props) {
// Customize the root error boundary for your microfrontend here.
return null;
},
domElementGetter,
});
export const { bootstrap, mount, unmount } = lifecycles;
TestPage.html Directly opened
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<script src="https://cdn.jsdelivr.net/npm/regenerator-runtime#0.13.5/runtime.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/import-map-overrides#2.3.0/dist/import-map-overrides.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs#6.8.3/dist/system.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs#6.8.3/dist/extras/amd.js"></script>
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa#5.9.0/lib/system/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react#16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom#16.13.1/umd/react-dom.production.min.js",
"rxjs": "https://cdn.jsdelivr.net/npm/#esm-bundle/rxjs/system/es2015/rxjs.min.js",
"rxjs/operators": "https://cdn.jsdelivr.net/npm/#esm-bundle/rxjs/system/es2015/rxjs-operators.min.js"
}
}
</script>
<script type="systemjs-importmap">
{
"imports": {
"#example/app": "http://localhost:8080/org-app.js"
}
}
</script>
</head>
<body>
<script>
System.import("#example/app");
</script>
<div id="example-app"></div>
<h1></h1>
<p>My first paragraph.</p>
<import-map-overrides-full
show-when-local-storage="devtools"
dev-libs
></import-map-overrides-full>
</body>
</html>
The problem here is that you're trying to bypass single-spa entirely. The root config should be where the applications get registered which creates the association between routes and applications and dictates when they will be mounted/unmounted. Simply calling System.import("#example/app"); is not enough because the applications do not manage their own lifecycles. Instead you could do something like this:
System.import("single-spa").then(({ registerApplication, start }) => {
registerApplication({
name: "#example/app",
app: () => System.import("#example/app"),
activeWhen: ["/"],
});
start({
urlRerouteOnly: true,
});
});
I cannot see a benefit do doing it this way over what create-single-spa provides.
Lastly, it seems that you're trying to do this to control where the applications are being mounted to. There are two ways to do this with what single-spa already provides:
Using single-spa-layout you can simply create all the intermediate DOM nodes
Use the domElementGetter option of the corresponding framework helpers to designate where the application should mount to.
I'm creating a whatsapp clone using next.js. On the first load of app i'm getting this error.
Warning: Expected server HTML to contain a matching <div> in <div>.
at div
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1621219033615:37232:19450)
at div
at Paper (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:45091:23)
at WithStyles (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:64751:31)
at div
at Drawer (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:33839:29)
at WithStyles (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:64751:31)
at SideBar (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:67329:75)
at div
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1621219033615:37232:19450)
at Chat (http://localhost:3000/_next/static/chunks/pages/chat.js?ts=1621219033615:73282:70)
at SideMenuProvider (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1621219033615:25916:23)
at MyApp (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1621219033615:31532:24)
at ErrorBoundary (http://localhost:3000/_next/static/chunks/main.js?ts=1621219033615:726:47)
at ReactDevOverlay (http://localhost:3000/_next/static/chunks/main.js?ts=1621219033615:829:23)
at Container (http://localhost:3000/_next/static/chunks/main.js?ts=1621219033615:8388:5)
at AppContainer (http://localhost:3000/_next/static/chunks/main.js?ts=1621219033615:8876:24)
at Root (http://localhost:3000/_next/static/chunks/main.js?ts=1621219033615:9012:25)
I'm totally unaware of the source of this error. and they also did not specified from which file, the error is occurred.
Because of this error App UI gets shatter from this
Actual UI of APP
to this
Errored UI of APP
If anybody have any idea why this is happening please help me.
_document.js code here
import React from "react";
import Document, { Html, Main, NextScript, Head } from "next/document";
import { ServerStyleSheets } from "#material-ui/core/styles";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* Meta Tags for SEO */}
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Comatible" content="IE=edge" />
<meta
name="description"
content="A WhatsApp clone made using next js and firebase."
/>
<meta name="keywords" content="WhatsApp Clone" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
You may need to import your components dynamically:
const MyDynamicComponent = dynamic(() => import('./myComponent'))
I'm getting started with Svelte and building a single-page application (using page.js as the router). I thought I'd have a separate component to produce the <svelte:head> block, and when each component mounts it would write the page title to a store, which would then be read by the head component. It partially works - the page title is updated when I click through to different pages. However, if I go back in my browser's history, the title doesn't change back with the page. It does change if I then reload the page. So perhaps onMount() isn't the right lifecycle method. What approach can I take that will work with history state navigation?
Here's my app boiled down to a minimal example.
// index.js
import page from 'page'
import App from './views/App.svelte'
const app = new App({
target: document.body,
props: {
route: null,
},
})
function one() {
app.$set({ route: 'one' })
}
function two() {
app.$set({ route: 'two' })
}
page('/one', one)
page('/two', two)
page()
// App.svelte
<script>
import One from './One.svelte'
import Two from './Two.svelte'
import Head from '../parts/Head.svelte'
import Home from './Home.svelte'
export let route
</script>
<Head />
{#if route === 'one'}
<One />
{:else if route === 'two'}
<Two />
{:else}
<Home />
{/if}
// Head.svelte
<script>
import { pageName } from '../stores.js'
let displayPageName
pageName.subscribe(value => {
displayPageName = value
})
</script>
<svelte:head>
{#if displayPageName}
<title>Test App — {displayPageName}</title>
{:else}
<title>Test App</title>
{/if}
</svelte:head>
// stores.js
import { writable } from 'svelte/store'
export const pageName = writable(null)
// Home.svelte
One Two
// One.svelte
<script>
import { onMount } from 'svelte'
import { pageName } from '../stores.js'
onMount(async () => {
pageName.update(() => 'Component One')
})
</script>
Two
// Two.svelte
<script>
import { onMount } from 'svelte'
import { pageName } from '../stores.js'
onMount(async () => {
pageName.update(() => 'Component Two')
})
</script>
One
For some reasons, Svelte doesn't seem to like the <title> in an {#if} block... I recommend computing the value in a reactive statement instead.
$: title = $pageName ? `Test App \u2014 ${$pageName}` : 'Test App'
$pageName is a special syntax to access the store's value without having to handle subscribe/unsubscribe yourself (docs).
Even with that, I found that my browser (tested in Chrome) was apparently OK with ignoring the <title> in the DOM when navigating back. We can force the value of document.title to workaround that. Another reactive block can take care of that:
$: {
document.title = title
}
So your whole Head.svelte component would now looks like this:
<script>
import { pageName } from '../stores.js'
$: title = $pageName ? `Test App \u2014 ${$pageName}` : 'Test App'
$: {
document.title = title
}
</script>
<svelte:head>
<title>{title}</title>
</svelte:head>
And finally, in your example, you're not updating the store's value when you navigate to /, so you'd have a stale title in this case. I think you need to add a route like so:
page('/', () => app.$set({ route: null }))
I am struggling to find an example of how to set the public path of an output file of a webpack bundle.
The documentation says:
If you don't know the publicPath while compiling, you can omit it and
set __webpack_public_path__ on your entry point.
Like so:
__webpack_public_path__ = myRuntimePublicPath
Would anyone be kind enough to create a JSFiddle example how this can be done?
Nothing has changed after almost two years. It's still surprisingly difficult to find an example of setting public path for webpack at runtime.
Prerequisites
webpack 4.5.0
an app big enough to leverage code splitting
For simplicity let's say our html lives in index.html and app entry point is app.js
An example that works
index.html
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="app"></div>
<script type="application/javascript">
window.resourceBasePath = '/path/to/scripts/on/server/';
</script>
<script type="application/javascript" src="/path/to/scripts/on/server/app.js"></script>
</body>
</html>
app.js
// it is important to set global var before any imports
__webpack_public_path__ = window.resourceBasePath;
import React from 'react';
import ReactDOM from 'react-dom';
import {store, history} from './store';
const render = () => {
import('./root').then((module) => {
const Root = module.default;
ReactDOM.render(
<Root
store={store}
history={history}
/>,
document.getElementById('app'),
);
});
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}
An example that doesn't work
Webpack publicPath documentation says it's enough just to set a global variable with the right name. I did that:
index.html
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<div id="app"></div>
<script type="application/javascript">
__webpack_public_path__ = '/path/to/scripts/on/server/';
</script>
<script type="application/javascript" src="/path/to/scripts/on/server/app.js"></script>
</body>
</html>
app.js
import React from 'react';
import ReactDOM from 'react-dom';
import {store, history} from './store';
const render = () => {
import('./root').then((module) => {
const Root = module.default;
ReactDOM.render(
<Root
store={store}
history={history}
/>,
document.getElementById('app'),
);
});
};
render();
if (module.hot) {
module.hot.accept('./root', render);
}
In this case my app fails complaining in console it couldn't load 0.js from current path to index.html. Which means that setting public path didn't have any impact.