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.
Related
I'm trying to pass data from Express backend to React frontend with Axios, but It doesn't work. The data I'm trying to pass comes from a text file.
Backend:
const express = require('express');
const app = express();
var fs = require('fs')
var data1 = fs.readFileSync('./inputs/category1.txt', 'utf8')
var textByLine1 = data1.split('\r\n')
var data2 = fs.readFileSync('./inputs/category2.txt', 'utf8')
var textByLine2 = data2.split('\n')
console.log(textByLine1)
app.get("/", function(req, res){
res.send(textByLine1)
})
app.listen(3001, function(){
console.log("express server is running on port 3001");
})
Axios:
import axios from 'axios';
export type ApiClient = {
getProperties: (c:number) => Promise<string[]>;
}
/** get tickets with search and page queries */
export const createApiClient = (): ApiClient => {
return {
getProperties: async (c:number) => {
const res = await axios.get('/', { params: { category: c} });
return res.data;
}
}
}
export default createApiClient
React frontend:
import React from 'react';
import './App.css';
import {createApiClient} from './api'; //connection to server
export type AppState = {
flow: number,
category_index: number,
categories: string[],
}
const api = createApiClient()
export class App extends React.PureComponent<{}, AppState> {
state: AppState = {
flow: 1,
category_index: 2,
categories:[]
}
async componentDidMount() {
this.setState({
categories: await api.getProperties(this.state.flow)
});
console.log(this.state.categories)
}
render() {
return(
<main>
<div>
<button>click me</button>
</div>
</main>
)
}
}
export default App;
I'm trying to pass the list of strings from the backend to the frontend, than console.log it to see that it was actually passed. instead, I see the following in the console:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<!--
Notice the use of in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="/static/js/bundle.js"></script><script src="/static/js/vendors~main.chunk.js"></script><script src="/static/js/main.chunk.js"></script></body>
</html>
I think that means I'm getting "empty" in the axios.get(). What am I doing wrong?
To call your API, you’ll need to call the URL that your API is serving, which I believe is http://localhost:3001/.
Try this:
const res = await axios.get('http://localhost:3001/', { params: { category: c} });
I'm trying to add a clucth.co widget to a Gatsby site, but it does not render. I've tried using react Helmet for the <script> part, but it still does not work.
Hopefully I'm missing something simple here, but looking at other solutions I can't find anything that works.
For reference: https://clutch.co/content/add-review-widget-your-website
<script type="text/javascript" src="https://widget.clutch.co/static/js/widget.js"></script>
<div className="clutch-widget" data-url="https://widget.clutch.co" data-widget-type="7" data-height="65" data-clutchcompany-id="XXXXXXX"></div>
You have multiple ways of inserting a third-party script in Gatsby. The problem you'll face in all of them is that you need to await that your div:
<div className="clutch-widget" data-url="https://widget.clutch.co" data-widget-type="7" data-height="65" data-clutchcompany-id="XXXXXXX"></div>
Needs to be rendered your script won't be able to load.
Using Script component (2022 update)
Since the release of the Script Gatsby component (powered by Partytown) it's much easier adding third-party scripts. Just:
import React from "react"
import { Script } from "gatsby"
function YourPage() {
return <Script src="https://my-example-script" />
}
export default YourPage
Using Helmet:
You said you already tried but it should. You may need to try the drop-in support that adds the gatsby-plugin-react-helmet. Then:
<Layout>
<SEO title="Live" />
<Helmet>
<script src="https://tlk.io/embed.js" type="text/javascript"/>
</Helmet>
</Layout>
Check the compatibility issues when used with hooks.
Using onRenderBody API from gatsby-ssr.js:
Gatsby exposes a setHeadComponents function in the onRenderBodyAPI that you can take advantage from:
import React from "react"
export const onRenderBody = ({ setHeadComponents }, pluginOptions) => {
setHeadComponents([
<script key="tracking"
src="https://widget.clutch.co/static/js/widget.js
type="text/javascript"
async
/>,
])
}
This snippet above will insert the <script> in the <head> tag of the compiled HTML.
Here you have another approach using dangerouslySetInnerHTML:
setHeadComponents([
<script dangerouslySetInnerHTML={{whateveryouneedtoset}}>
])
Extracted from Unable to Inject 3rd Party Scripts in Gatsby
Modifying directly the html.js:
You can customize even more the output of the resultant HTML by modifying the html.js, the boilerplate that uses Gatsby to build your entire site.
Run:
cp .cache/default-html.js src/html.js
Or alternatively, copy the default-html.js from .cache folder into /src and rename it to html.js. When compiling, if the html.js is present, Gatsby will take it to build your site based on that skeleton.
You'll have something like:
import React from "react"
import PropTypes from "prop-types"
export default function HTML(props) {
return (
<html {...props.htmlAttributes}>
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
{props.headComponents}
</head>
<body {...props.bodyAttributes}>
{props.preBodyComponents}
<div
key={`body`}
id="___gatsby"
dangerouslySetInnerHTML={{ __html: props.body }}
/>
{props.postBodyComponents}
</body>
</html>
)
}
HTML.propTypes = {
htmlAttributes: PropTypes.object,
headComponents: PropTypes.array,
bodyAttributes: PropTypes.object,
preBodyComponents: PropTypes.array,
body: PropTypes.string,
postBodyComponents: PropTypes.array,
}
There you can add your <script> directly:
<head>
<meta charSet="utf-8" />
<meta httpEquiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<script type="text/javascript" src="https://widget.clutch.co/static/js/widget.js"></script>
{props.headComponents}
</head>
Using gatsby-plugin-load-script:
Just install and use the plugin:
{
resolve: 'gatsby-plugin-load-script',
options: {
src: 'https://widget.clutch.co/static/js/widget.js',
},
},
Hacking the gatsby-browser.js API:
If none of the above fits you, you can still use one of gatsby-browser.js APIs (onClientEntry) to manually add script given a source URL:
const addScript = url => {
const script = document.createElement("script")
script.src = url
script.async = true
document.body.appendChild(script)
}
export const onClientEntry = () => {
window.onload = () => {
addScript("https://widget.clutch.co/static/js/widget.js")
}
}
In order to not have the clutch widget disappear on route changes, I ended up running the Init and Destroy methods from window.CLUTCHCO myself in useEffect.
React.useEffect(() => {
// add widget to end of body and run it
const script = document.createElement("script")
script.type = "text/javascript"
script.src = "https://widget.clutch.co/static/js/widget.js"
script.async = true
document.body.appendChild(script)
// run script
script.onload = () => {
// #ts-expect-error Apparently we have to manually do this!! 🗑️
// eslint-disable-next-line #typescript-eslint/no-unsafe-member-access, #typescript-eslint/no-unsafe-call
window.CLUTCHCO.Init()
}
return () => {
// #ts-expect-error Apparently we have to manually do this!! 🗑️
// eslint-disable-next-line #typescript-eslint/no-unsafe-member-access, #typescript-eslint/no-unsafe-call
window.CLUTCHCO.Destroy()
document.body.removeChild(script)
}
}, [])
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'))
Setup
I've initialized a new project using vite on an Arch based operating system.
When I try to create the simple counter from the vue docs, the elemet doesn't render.
Code
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="counter">
Counter: {{ counter }}
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
main.js
import { createApp } from 'vue'
var CounterApp = {
data() {
return {
counter: 0
}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
}
}
createApp(CounterApp).mount('#counter')
When I inspect the element it is commented out:
Question
Why is that? And how to resolve the error?
Doing that replaces the normal mounting process, and treats the root element like a template string for the App component. Since template strings require the runtime compiler, you would need to use a full build. There should be a console warning about that.
To avoid increasing the size of your app (by ~30%) with the full build, it's recommended to leave the mounting point untouched, and give the App component its own proper template:
index.html
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
App.vue
<template>
<div id="counter">
Counter: {{ counter }}
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
counter: 0
}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
}
}
</script>
By default the runtime compiler is not included in the Vue build.
To include it, add the following resolve.alias configuration:
vite.config.js
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
},
},
})
Docs https://vitejs.dev/config/#resolve-alias
As a simple example, suppose I had these two files:
example.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8" />
<title>Button example</title>
<script type="text/javascript" src="ButtonHandler.js"></script>
</head>
<body id="body" onload="init()">
<button onclick=buttonHandler.writeToConsole()>Button</button>
<script>
function init() {
buttonHandler = new ButtonHandler();
}
</script>
</body>
</html>
ButtonHandler.js
function ButtonHandler() {
};
ButtonHandler.prototype.writeToConsole = function () {
console.log('Writing');
}
This simply prints to the console whenever the button is clicked.
Ignore that the ButtonHandler's constructor is empty, and that I could just easily call 'console.log' in the onclick directly. This is a simplified version of an issue I'm having, with several classes.
My question is, how would I go about translating this to React/JSX, ideally without modifying the Javascript files (in this case, just ButtonHandler.js). Ideally this means no exporting/importing, I'd like to do it how the HTML file does it - it just links to the script in the <\head>.
The closest I have is something like this:
convert.jsx
import * as React from 'react';
export default class Entry extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
buttonHandler = new ButtonHandler();
}
render() {
return (
<div>
<title>Button example</title>
<button onclick="buttonHandler.writeToConsole()">Button</button>
</div>
)
}
}
But I get the error that ButtonHandler is undefined. I followed this stackexchange answer and placed
<script type="text/javascript" src="[rest of path...]/ButtonHandler.js"></script>
in the public/index head, and I added the 'window.ButtonHandler' in componentDidMount(), but I still get the error that it's undefined.
Am I doing something wrong, and if not, what other approach can I take?
edit: When I put ButtonHandler.js in the public folder with index, and I console log the window, I see it appear as a function of window, like the stackexchange answer describes. This doesn't happen when I have it in another folder, though. Same error however.
edit 2: Seems the only solution is to put ButtonHandler.js in the public folder and then call it in the constructor like the selected answer says. Then add a
<button onClick={() => this.buttonHandler.writeToConsole()}>Button</button>
to call it.
In create react app, you should be able to add any js files to your public folder for use in your project. You just need to reference the files in your script like:
<script type="text/javascript" src="%PUBLIC_URL%/ButtonHandler.js"></script>
That will make sure that it looks in the public folder when building.
The only problem with that is that the files won't be minified in the bundle.
Edit
You will have to reference the global variable inside your component as well.
/* global ButtonHandler */
import * as React from 'react';
export default class Entry extends React.Component {
constructor(props) {
super(props);
this.buttonHandler = new ButtonHandler();
}
render() {
return (
<div>
<title>Button example</title>
<button onclick={this.buttonHandler.writeToConsole}>Button</button>
</div>
)
}
}
You'll want to import that ButtonHandler js code. If it's not something you've written yourself, the easiest thing to do would be to see if it already exists as a React package. If it's your own file, then you'll want to export the functions in ButtonHandler.js, import ButtonHandler in your React component, then you'll have access to them in the component.
ButtonHandler.js
export function writeToConsole() {
console.log('Writing');
}
convert.jsx
import React, { Component } from 'react';
import { writeToConsole } from './ButtonHandler';
export default class Entry extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<title>Button example</title>
<button onclick={this.writeToConsole}>Button</button>
</div>
)
}
}