I have a project in a middle of development. And I need to use external landing page as a home page. Therefore, I need to import landings index.html, but it has its own folders with css and js(mainly Jquery code).
I wanted to import it as <iframe src={html}></iframe> into my project but my app doesn't seem to load htmls.
What are best ways to import html files that use own jquery code to react?
This is a bit tricky, and there might be other ways (perhaps better) to achieve the same result. Also, I would consider the performance impact of loading multiple libraries into an existing React app.
With that humble disclaimer out of the way, one way to do this would be to include jQuery directly into React's main index.html page using <script> tags, this will make $ globally available across the app:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/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="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</body>
</html>
Once this is done, place the landing page project folder (along with its dependencies) inside the public directory:
Then, from the main app component load the desired landing page using fetch, then use .text() to transform the retrieved page into regular text.
Use setState to set the retrieved HTML into the app state, then inside render() use a regular <div> container to store the landing page and use the React attribute dangerouslySetInnerHTML to set HTML inside that target container.
Finally, I pass an anonymous function (as a second parameter to setState) and use jQuery's getScript() to load and execute the required JS libraries that the landing page depends on.
In the example, I loaded Bootstrap's JS, which is needed to power the Carousel.
Bootstrap's CSS is loaded directly from the landing page's HTML file using a standard <link> tag.
import React from "react";
class App extends React.Component {
state = {
page: null
};
componentDidMount() {
fetch("landing-page-one/index.html")
.then(result => {
return result.text();
})
.then(page => {
this.setState(
{
page: { __html: page }
},
() => {
window.$.getScript("landing-page-one/js/index.js");
}
);
});
}
render() {
const { page } = this.state;
return (
<>
<h2>
<span>Inserting project using React's </span>
<code>dangerouslySetInnerHTML</code>:
</h2>
<div
dangerouslySetInnerHTML={page && page}
/>
</>
);
}
}
export default App;
Working example here.
I'm just going to through this out there as a spitball... I've never tried it, but why not directly replace index.html and not render anything on that page? You would have to either adjust the build somehow... I have no idea how to, or manually put it after the react app finishes building. I'm sure it would be a bug nightmare, but it honestly sounds a little less of a hastle than an iframe which might give you some weird UI behavior. React-Server, even though it says server, really just takes stringified JSX and I think html translates it back to workable code. You could also try that approach, but this does sound like a nightmare project.
I was also trying to find out a solution and figured out two ways for different use cases:
If your HTML file contains <div> and other child tags (that can be nested under other <div> or <body> tags) then this approach will work for you:
First you need to configure webpack to be able to parse HTML files.
You can either do that with npm eject or use another module
react-app-rewired - and then add a HTML loader in overrides.
Then import the HTML file and use a parser. I believe it's better than using dangerouslySetInnerHTML.
Example with Method 1:
const parse = require("html-react-parser");
const docs = require("../../public/redoc-static.html").default;
const ApiDocs = (props: any) => {
return (
<Box sx={{ display: "flex", height: "100vh" }}>
<Box>{parse(docs)}</Box>
</Box>
);
};
If your HTML file contains a <html> tag (is a complete file) following the above approach would give the very obvious error <html> cannot appear as a child of <div>. In that case, the workaround I found is to open the whole HTML file in a different tab of the browser with _blank:
Example with Method 2:
window.open(filename, "_blank")
Note: The HTML file has to reside in the public folder of your project.
Related
I'm building a small application, and planning to use web components (rather than use a UI library). I don't plan to use any bundlers etc., as this is going to be a small personal site.
I would like to store each web component in a separate ES6 JS module file, and here is an example of my setup:
hello-planet.mjs
export class HelloPlanet extends HTMLElement {
constructor() {
super();
}
connectedCallback(){
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
let planet = this.getAttribute('planet')
shadowRoot.innerHTML = `<p>hello ${planet}</p>`;
}
}
hello-world.mjs
export class HelloWorld extends HTMLElement {
constructor(){
super()
}
connectedCallback(){
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
['Mercury','Venus','Earth','Mars','Jupiter','Saturn','Uranus','Neptune'].forEach(planet=>{
let el = document.createElement('hello-planet')
el.setAttribute('planet', planet);
shadowRoot.appendChild(el)
})
}
}
main.mjs
// ordering of imports does not matter
import {HelloPlanet} from './hello-planet.mjs';
import {HelloWorld} from './hello-world.mjs';
customElements.define('hello-world', HelloWorld);
customElements.define('hello-planet', HelloPlanet);
// this will typically be handled by a client side router (e.g. Navigo)
let el = document.createElement('hello-world');
let body = document.querySelector('body');
body.appendChild(el);
index.html
(only calls main.mjs, browser will download the rest of the scripts)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="main.mjs" type="module"></script>
<title>Web components test</title>
</head>
<body>
</body>
</html>
Questions:
I've not seen this approach in any examples I've encountered so far, so wondering if
is a good approach? Or there is a better approach than this in terms
of organizing web components.
When I need template+styles, how can
this approach be extended to read those from different files i.e. html and css
in separate files (so we have separation of concerns)?
I've seen this but not sure how to adapt to my kind of setup. I've also gone
through this - but it seems too complex already, and not even sure
if it can handle complex scripting.
Thank you!
I've not seen this approach in any examples I've encountered so far, so wondering if is a good approach? Or there is a better approach than this in terms of organizing web components.
It's perfectly fine. Creating your elements programmatically has many advantages, mainly there is no need to query your own shadow root to get access to child elements/components. If need be, you can directly hold references or even create those in class properties, e.g.:
export class HelloWorld extends HTMLElement {
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
.map(
planet => Object.assign(document.createElement('hello-planet'), { planet })
)
)
constructor() {
super().attachShadow({ mode: 'open' }).append(...this.planets);
}
}
Sidenote: Creating the shadow root can and should safely be done in the constructor.
When I need template+styles, how can this approach be extended to read those from different files i.e. html and css in separate files (so we have separation of concerns)?
For CSS, we have CSS module scripts:
import styles from './hello-world.css' assert { type: 'css' }
then, in your constructor, do
constructor() {
// ...
this.shadowRoot.adoptedStylesheets.push(styles);
}
For HTML, this importing feature unfortunately is still work in progress.
Using import assert to load the component's HTML and CSS may be the generic approach moving forward, however, browser support is still limited. To create web components with a separation of concerns and limited build tools (like bundlers) you can use standard fetch with top level await to ensure the component's bootstrapped appropriately before using.
I similarly wanted to separate concerns and minimize tooling while developing modern web components as ES modules. I've started documentation (WIP) on web component best practices that I wanted to follow while developing tts-element.
This is the relevant setup work to bootstrap the component during its import:
const setup = async () => {
const url = new URL(import.meta.url)
const directory = url.pathname.substring(0, url.pathname.lastIndexOf('/'))
const baseUrl = `${url.origin}${directory}`
const [html, css] = await Promise.all([
fetch(`${baseUrl}/template.html`).then((resp) => resp.text()),
fetch(`${baseUrl}/styles.css`).then((resp) => resp.text())
])
const parser = new DOMParser()
const template = parser
.parseFromString(html, 'text/html')
.querySelector('template') as HTMLTemplateElement
const style = document.createElement('style')
style.textContent = css
template.content.prepend(style)
return class TextToSpeech extends HTMLElement {/* implementation */}
}
Here is how the component will be exported to allow top level await to be used:
export default await setup()
Here is an example of how tts-element can be loaded in various ways (all using top level await) to control the timing of when the component is defined.
Loading of defined.js will automatically register the component under the default name text-to-speech.
Using the name query parameter while loading defined.js?name=custom-name will register the component with the provided custom name.
Loading of element.js will require manual registration of the components definition, i.e. the consumer will be responsible for calling define().
Check the network tab in the dev console to see how the HTML/CSS/JS is loaded:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>tts-element combined example</title>
<style>
text-to-speech:not(:defined), my-tts:not(:defined), speech-synth:not(:defined) {
display: none;
}
</style>
<script type="module" src="https://unpkg.com/tts-element/dist/text-to-speech/defined.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/tts-element#0.0.3-beta/dist/text-to-speech/defined.js?name=my-tts"></script>
<script type="module">
import ctor from 'https://unpkg.com/tts-element/dist/text-to-speech/element.js'
customElements.define('speech-synth', ctor)
</script>
</head>
<body>
<text-to-speech>Your run-of-the-mill text-to-speech example.</text-to-speech>
<my-tts>Example using the "name" query parameter.</my-tts>
<speech-synth>Example using element.js.</speech-synth>
</body>
</html>
The way they do it in react is you import all your components into one file and then you just insert that component into a statice div with an id of root in your HTML page.
So your index file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="main.mjs" type="module"></script>
<title>Web components test</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Than anything you want to display in the DOM you insert into that div via JS.
I believe this might help your problem, honestly wasn't a 100% clear on your isssue
I had similar questions regarding separating HTML and CSS, but had a hard time finding a solution I wanted.
The "CSS Module Script" that connexo suggest sounded great, but doesn't seem to be supported by Safari.
Here's how I solved this - in case this might resonate with you as well.
I built a CLI tool to use with PHPStorm and VSCode. The IDE will automatically detect changes in HTML and CSS and compile it automatically to JS/TS modules. No need for Webpack, Rollup or similar tools.
I have shared it here: https://www.npmjs.com/package/csshtml-module
It can compile a single CSS or HTML file, or it can bundle both in a HTMLTemplate module.
Example: this (button.css) 👇🏻
button {
background-color: red;
}
automatically becomes this (button.style.ts) when css file has changed 👇🏻
// language=css
export const css: string = `button {
background-color: red;
}`;
And I can use plain JS/TS to get it:
import {css} from './button.style.js'
const style = document.createElement('style');
style.innerHTML = css;
And since the IDE compile on save, it is almost instant changes between CSS/HTML edits and result.
How can one prevent the page flash when using class-based dark mode in Tailwind CSS with Next.js v12 without using any 3rd party pkgs like next-themes?
I've looked at:
this Q&A How to fix dark mode background color flicker in NextJS? and while it's a valid/working solution in Next.js <v12, in v12 it no longer works and throws warnings in dev which suprisingly turn to a build blocking error in prod env
Do not add <script> tags using next/head (see inline <script>). Use next/script instead. See more info here: https://nextjs.org/docs/messages/no-script-tags-in-head-component
This article https://www.vidyasource.com/blog/dark-mode-nextjs-tailwindcss-react-hooks however including the script <Script strategy="beforeInteractive" src="/scripts/darkMode.js"/> still results in a page flash as it adds defer to it in the head
This official Tailwind CSS dark mode doc on what's required https://tailwindcss.com/docs/dark-mode#toggling-dark-mode-manually
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
I think they restrict putting things in Head from v12 to prepare for Suspense / Streaming / React v18.
Either way, I'm lost on how to do it without next-themes, does anyone know how can we just inject that bit of script to prevent that page flash?
Hope this question makes sense, if not, please give me a shout.
I like simple and minimalistic things, hence the aim to reduce the dependency on 3rd party pkgs, such a simple thing should be possible without overcomplicated solution IMO.
I had the same problem and solved it like this in Next 12.1.0:
Create theme.js within the public folder (mine has the following content):
;(function initTheme() {
var theme = localStorage.getItem('theme') || 'light'
if (theme === 'dark') {
document.querySelector('html').classList.add('dark')
}
})()
Add <Script src="/theme.js" strategy="beforeInteractive" /> to _app.tsx or _app.jsx. This is important - I tried putting it inside _document.tsx which didn't work. The final _app.tsx looks like this:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Head from 'next/head'
import Script from 'next/script'
function App({ Component, pageProps }: AppProps) {
return <>
<Head>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Script src="/theme.js" strategy="beforeInteractive" />
<Component {...pageProps} />
</>
}
export default App
Works like a charm for me - no flickering and all Next.js magic. :) Hope this works for you too.
I'm integrating a mailerlite popup for a client's next.js project, and I'm having a difficult time converting the JavaScript snippets into the jsx required to make the popups function properly. On first load it seems to work just fine, but on relaod I'm getting the following error.
window is not defined
I've encountered the issue while dealing with DOM manipulation, but in this case, judging from the code in the snippet, I need the window object.
Install the following snippet of Javascript on every page of your website right before the closing tag.You only need to add this snippet once, even if you plan to have a few different webforms.
<!-- MailerLite Universal -->
<script>
(function(m,a,i,l,e,r){ m['MailerLiteObject']=e;function f(){
var c={ a:arguments,q:[]};var r=this.push(c);return "number"!=typeof r?r:f.bind(c.q);}
f.q=f.q||[];m[e]=m[e]||f.bind(f.q);m[e].q=m[e].q||f.q;r=a.createElement(i);
var _=a.getElementsByTagName(i)[0];r.async=1;r.src=l+'?v'+(~~(new Date().getTime()/1000000));
_.parentNode.insertBefore(r,_);})(window, document, 'script', 'https://static.mailerlite.com/js/universal.js', 'ml');
var ml_account = ml('accounts', '912433', 'd5p1f7l9g0', 'load');
</script>
<!-- End MailerLite Universal -->
I've placed this code in my Layout wrapper. As previously stated, it works fine on first load, but as soon as the user navigates to a new page above error shows up.
PS I found an old question regarding this topic here, but it's old and not quite relevant to my situation. I need to figure out how to convert the above snippet for nextjs. Any help at all would be appreciated.
This approach treats the MailerLite universal tag as its own <script> hosted on your site's domain.
Add a NextJS custom document.
Create a JavaScript file containing the MailerLite universal tag code in ./public. I put mine in ./public/scripts/ml.js.
Add a <script> tag loading #2 in your custom _document.js file:
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html>
<Head>
<script async src="/scripts/ml.js"></script>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
Everything worked as intended from there! (Caveat: I'm only using embedded forms).
I created a Polymer 2.0 app from the starter kit template in order to get to know the framework a little bit. I have a paper-button that should link to another page of the app. However, I still haven't figured out how to do so, since Polymer is loading pages dynamically via JavaScript rather than the browser just calling another one.
I also noticed something else strange: When I click a link in my app-drawer, the page changes automatically and the URL in my browser tab is being updated. However, when I hit refresh with that new URL in my address bar, I get a 404 error since the URL doesn't exist. So is there any way I can resolve this issue and link my button to another page?
This is my button:
<paper-button id="buttonStartQuiz" on-click="startQuiz">
go! <iron-icon icon="chevron-right"></iron-icon>
</paper-button>
And this is the JavaScript class that corresponds to the layout:
class MyView1 extends Polymer.Element {
static get is() { return 'my-view1'; }
/* This method is the listener for the button click */
startQuiz(e) {
// Found this on a website, but doesn't work
this.set('route.path', '/view-question');
}
}
window.customElements.define(MyView1.is, MyView1);
I don't know if it's any useful, but here are my imports in case you need to know.
<link rel="import" href="../bower_components/polymer/polymer-element.html">
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../bower_components/paper-input/paper-input.html">
<link rel="import" href="../bower_components/paper-button/paper-button.html">
The fact is Polymer doesn't do that, some element (app-route which implement with Polymer) do that. The Polymer itself is the library that help you work with custom element easier.
This behavior done by JavaScript and History API. See how to use it on mdn. An application like this, dynamically rewriting the current page rather than loading entire new pages its called a single-page application (SPA).
Basically application like this have only one page (index.html). When you try to load from another path the server will cannot find it.
You can resolve this by config the server to serve every path you used with index.html. For development you can easily use polymer serve command from polymer-cli see here.
To link to another page you can done by many ways:
=> Wrap your element with <a>:
<a href='/another-page'>
<paper-button>...</paper-button>
</a>
=> Change route variable from app-location: in my-app.html
<app-location route='{{route}}'></app-location>
...
<paper-button on-click='changeRoute'>...</paper-button>
class MyApp extends Polymer.Element {
...
changeRoute () {
this.set('route.path', '/another-page')
}
...
}
If you want to do this in your file just import and use app-location.
=> Use History API
window.history.pushState({}, null, '/another-page');
// Notify to `app-location`
window.dispatchEvent(new CustomEvent('location-changed'))
Obviously, that's not an easy task, as the only thing that changes in the html.js template file by default are the head meta tags and the content.
The meta tags are handled by the Helmet component ({head.title.toComponent()} and {head.meta.toComponent()}) and the HTML changes inside the template are managed by React. (<div id="react-mount" dangerouslySetInnerHTML={{ __html: this.props.body }} />)
The body tag however is outside the scope of React, which is why I can't figure out how to change it on-the-fly when I navigate from page to page. That's exactly what I'd need though as I wanna apply a different body background to each page.
I know that I could solve this by using the exports.onRouteUpdate in gatsby-browser.js, but I would like the class to be present even if JS is disabled in the browser. Means I'd like it to be there even if I export without the bundle.js file, just generating the static site HTML.
React-helmet now supports adding attributes to body element as well.
So, if you want to add a class to a specific component/page, you can do something like this:
import Helmet from 'react-helmet'
// Inside your component
<Helmet
bodyAttributes={{
class: 'new-class-for-body'
}}
/>
// or
<Helmet>
<body className="new-class-for-body" />
</Helmet>
It does look like react-helmet supports dynamically/statically setting a class on the <html> element.
They don't want to support setting classes on the body though... https://github.com/nfl/react-helmet/issues/182
If you really need to support body classes, then this module does something very similar to react-helmet but for body classes https://github.com/iest/react-body-classname
February 2023:
Since the release of gatsby#5.5.0, the preferred way of doing this is using the Gatsby Head API. (The gatsby-plugin-react-helmet plugin has been deprecated.)
In your page file, export a <Head> component with a <body className /> element inside:
export const Head = () => <body className="your-class-name" />
You can also do this alongside other <Head> changes, e.g.:
export const Head = () => (
<>
<html lang="en" />
<title>Hello World</title>
<meta name="description" content="Hello World" />
<body className="home-page" />
</>
)