Dynamically creating an element on which the react app mounts - javascript

I want to create a build of my javascript application using react which currently is just a single file defined as below. It does nothing but creates a div and keeps on changing the color of the hello-world text after it mounts.
import * as React from "react";
import * as ReactDOM from "react-dom";
class App extends React.Component {
componentDidMount() {
const colorBox = ["red","blue","green"];
const element = document.getElementById("my-div");
setInterval(() => {
element.style.color = colorBox[parseInt(Math.floor(Math.random() * colorBox.length))];
}, 3000);
}
render() {
return (
<div id={"my-div"}>
Hello, World.
</div>
);
}
}
ReactDOM.render(<App />,
document.body.appendChild(document.createElement("div")));
I am using parcel js as a bundler. I bundle my javascript as parcel build index.js and this creates a build of my javascript file inside dist folder. So far, so good. But the moment, I load this javascript using a script tag on my website as:
<script type="text/javascript" src="https://mycdn.com/bundle.index.js"></script>
It throws an error
index.js:16 Uncaught TypeError: Cannot read property 'appendChild' of null
at Object.parcelRequire.Focm.react (index.js:16)
at f (index.js:1)
at parcelRequire.YOwE (index.js:1)
at index.js:1
Why it cannot access appendChild of document.body? I also checked that document.body is null. What is happening here? What is it that I am missing?

First of all I'm not sure this is a great idea to have code in the bundle that directly affects the page like this.
My guess as to why this is happening is it has to do with when the bundle is loaded in the page. Is it in head? If so document.body may not be available.
It is probably best if you are going to do something like this to use the events associated with the document being ready.
This can get complicated depending on your needs, but if you have access to jQuery, you can use .ready().
If not you can use various vanilla js options.

I guess your problem is related to the bundle or you ask for an element before it is available because it is working fine here: https://jsfiddle.net/rL6dnhps
If you load it from the head, try to put at the end of the body in order to wait for the body to be ready or wrap it inside an IIFE like below:
(function() {
// the DOM will be available here
ReactDOM.render(<App />,
document.body.appendChild(document.createElement("div")));
})();

Related

Using react component in a different application

What I'm trying to achieve is:
Building simple react app - the template is create react app
Copying output file (main.*.js)
Pasting it in another react app
Importing render function to render the first app into the second one
Simple react app code:
interface Props {
greeting: string;
}
export module AppModule {
export const sendGreetings = ({ greeting }: Props) => {
return `Hello ${greeting}`;
};
}
Builder file code:
!function(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}}();
Trying to import this file into another app I get this error:
File 'c:/vscode/test-react-app/test-sc-react/src/main.783e0281.js' is not a module.ts(2306)
Which is obvious. I changed the output file manually to:
export function initApp(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}};
It works but the only function that I'm able to access is initApp but not sendGreetings
I've been struggling with this for a while now and I would really appreciate any helpful suggestions
I used Bit.dev for my components that are used across multiple applications & there is an article regarding your issue
https://blog.bitsrc.io/sharing-react-components-across-multiple-applications-a407b5a15186
I think it would help.
🎯 Solution #1
You can use an iframe to inject your react app:
<iframe src='path-to-your-app.html'/>
🎯 Solution #2
Go with micro-frontend architecture approach. Where a front-end app is decomposed into individual, semi-independent "microapps" working loosely together.
As a starting point, you can try npx create-mf-app instead of the CRA.
You can include your js code directly on run time. You can use window.addEventListener to load js/css incoming from an outside source. You just have to append that js to your document on the load event.

Compiling markdown at run-time in a Svelte component using mdsvex

I'm trying to take a string containing markdown in a Svelte template and use that as part of a Svelte component using mdsvex I have the following code in App.svelte:
<script>
import { compile } from 'mdsvex';
import Counter from './lib/Counter.svelte'
const transformed_code = compile(`
# Hello world
This is a paragraph
<Counter />
`, {});
</script>
<div id="content"></div>
Once that's done, I want to add the result inside the #content box. When I run the compile step, I get the following error:
Uncaught (in promise) ReferenceError: process is not defined
What exactly am I doing wrong? And is this actually possible to do using mdsvex? Thanks for any help?
The module is intended to be used on the server and uses the Node variable process. The playground page shims the variable based on this: https://github.com/defunctzombie/node-process/blob/master/browser.js
You can define a process object like that and assign it on the window. Make sure that this code runs on the client:
window.process = { /* fake process here */ }
Once this is done, the code will execute, but the Counter will not work like this; the import does nothing. The output of mdsvex has to be compiled with Svelte, which generates code that tries to access an undefined Counter component.
There are multiple possible approaches:
Somehow inject the imported component constructor, e.g. via a prop (though I have run into errors because of an unset target option)
Precompile any component you want to use like this and make them available as a route that can imported from the compiled output
Compile the component on the fly. You will need to have some way to get the component source code for processing
If you have nested component imports, option 2 is probably the most viable, since you then can adjust all import URLs at the time you generate the compiled output.

How to use imported third-party js (not addon/npm package) in controller/component?

I have put my js files eva.min.js/feather.min.js and so on in vendor dir, then I imported them in ember-cli-build.js app.import('vendor/eva.min.js'). But how to use it?
I tried something like import eva from 'eva'/'eva.min'/'eva.min.js' or import Eva from 'eva'; and so on, but it doesn't work.
app.import('vendor/eva.min.js');
app.import('vendor/bootstrap.min.js');
app.import('vendor/feather.min.js');
app.import('vendor/popper.min.js');
app.import('vendor/jquery-slim.min.js');
app.import('vendor/swipe.js');
import Swipe from 'swipe';
Console usually gives me the could not find the module error.
And I don't have a deep background in programming, so I would highly appreciate if you explained the problem as simple as possible.
UPD: I found all js code as npm package (it happens that the js files weren't third-party)
https://www.npmjs.com/package/feather
https://www.npmjs.com/package/popper.js
https://www.npmjs.com/package/jquery-slim
https://www.npmjs.com/package/swipe
https://www.npmjs.com/package/bootstrap
https://www.npmjs.com/package/eva-icons
But all your responses were helpful. Anyway in the near future I expect to use third-party libraries.
A quick way is to use scriptjs and it allows you to load any javascript into your component in the following way: (I am using Yammer as an example)
import $scriptjs from 'scriptjs';
componentDidUpdate() {
//script loader
setTimeout(function(){
$scriptjs('https://c64.assets-yammer.com/assets/platform_embed.js',
() => {
window.yam.connect.embedFeed(YammerHelper.loadComments());
});
}, 1000);
}
You should get the idea how to consume it. Check their docs with lots of examples.
This is not the best solution. But one way of using the third party js is,
1) say you have a function in your js file vendor/third-party.js
someFunction = function (element) {
...
console.log("works")
};
2) Then import it in your ember-cli-build.js
...
app.import('vendor/third-party.js');
...
3) After importing restart your server.
Use the function directly in your controller/component as
window["someFunction"]
Unless the JavaScript library being used explicitly supports the import X from 'y' syntax then when you import in the build using the app.import syntax you just use it in your app just as the plugin documentation describes.
So for Swipe you would do the following.
Based on this documentation: https://github.com/thebird/Swipe
// ember-cli-build.js
app.import('myswipe.js`);
// component.js
/* global Swipe */ // This silences the linter from throwing errors...
classNames: ['swipe'],
didInsertElement() {
this._swipe = Swipe(this.element, {
option1: option1
});
}
// component.hbs
<div class='swipe-wrap'>
{{yield}}
</div>
This codes creates a component to control your swipe plugin.
This code would create a swipe object and isolate it to the component.
Again when you use the app.import you are just loading the library on boot. The library does whatever it says it will do in the docs. Sometimes they register a global object, sometimes they dont.

Next.js: How to dynamically import external client-side only React components into Server-Side-Rendering apps developed?

I know this question has been asked multiple times before but none of the solution seems to work.
I'm trying to use the library 'react-chat-popup' which only renders on client side in a SSR app.(built using next.js framework) The normal way to use this library is to call import {Chat} from 'react-chat-popup' and then render it directly as <Chat/>.
The solution I have found for SSR apps is to check if typedef of window !=== 'undefined' in the componentDidMount method before dynamically importing the library as importing the library normally alone would already cause the window is not defined error. So I found the link https://github.com/zeit/next.js/issues/2940 which suggested the following:
Chat = dynamic(import('react-chat-popup').then(m => {
const {Foo} = m;
Foo.__webpackChunkName = m.__webpackChunkName;
return Foo;
}));
However, my foo object becomes null when I do this. When I print out the m object in the callback, i get {"__webpackChunkName":"react_chat_popup_6445a148970fe64a2d707d15c41abb03"} How do I properly import the library and start using the <Chat/> element in this case?
Next js now has its own way of doing dynamic imports with no SSR.
import dynamic from 'next/dynamic'
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
Here is the link of their docs: next js
I've managed to resolve this by first declaring a variable at the top:
let Chat = ''
then doing the import this way in componentDidMount:
async componentDidMount(){
let result = await import('react-chat-popup')
Chat = result.Chat
this.setState({
appIsMounted: true
})
}
and finally render it like this:
<NoSSR>
{this.state.appIsMounted? <Chat/> : null}
</NoSSR>
You may not always want to include a module on server-side. For
example, when the module includes a library that only works in the
browser.
Import the library normally in child component and import that component dynamically on parent component.
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
This approach worked for me.

Import or require a bundled js file created by react application

Let's say I have a normal react application using redux and some ajax calls.
If I want to pass it to someone I will give them the bundled js file I created with webpack and ask them to include it in their HTML + render a div with an id of "myApp" for example:
<div id="myApp"></div>
Ok, what if their website is also created with react, and they want to include my bundled js file inside one of their components, and of course render the relevant div?
I tried to use import or require to simulate this:
require('./path/to/myBundle.js');
import './path/to/myBundle.js';
Example:
//...
import './path/to/myBundle.js'; // the file that will render myApp to the relevant div
// ....
export function SomeApp(args){
return(
<div>
<div id="myApp"></div>
<SomeComponent />
</div>
);
};`
This does not work as I get some errors about:
Uncaught Error: Minified React error #37; visit
http://facebook.github.io/react/docs/error-decoder.html?invariant=37
for the full message or use the non-minified dev environment for full
errors and additional helpful warnings.
And when I visit this site I see:
_registerComponent(...): Target container is not a DOM element.
However, if they'll use this file (myBundle.js) outside their components (top level index.html for example) it will work just fine of course.
EDIT:
I forgot to mention that I think I know what the problem is, the application doesn't have the HTML ready with this div yet. but I don't know a good and native way to wait for it to exist.
EDIT #2 following #Frxstrem 's answer:
I'm trying to follow this answer but I think I'm doing it wrong.
I have 2 copies of corry house slingshot demo app as app1 and app2.
changed the 'output' on webpack.config.prod.js of app1 to:
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'app1Bundle.js',
library: "App1",
libraryTarget: "umd"
},
I'm trying to render app1 inside the homepage component of app2.
so i copied the "published" files from app1 to the root of app2 and called the folder app1, then added an import call:
import {app1} from '../../app1/app1Bundle';
and a matching tag inside the return function:
const HomePage = () => {
return (
<div>
<app1 />
<h1>App 2</h1>
</div>
);
};
I get the same error as I posted above.
I also tried different combinations:
import app1 from '../../app1/app1Bundle'; // without curly braces
or even just getting the script as a normal js script
import '../../app1/app1Bundle';
or
require('../../app1/app1Bundle');
and then tried to render a normal div tag with an id of "app1"
const HomePage = () => {
return (
<div>
<div id="app1"></div>
<h1>App 2</h1>
</div>
);
};
nothing seems to work as I still get the same error.
I think the problem is the timing of the script load and the rendering of the elements. I think the div does not exist yet when the bundled script is searching for it.
By default, Webpack will expose the entry module as a variable, which is useful when you include scripts with a <script> tag. (Because of this, if you require it you would likely just get {}.) However, if you want to load your bundle from other modules, you'll need to tell Webpack to expose it as an exported module instead.
The easiest way to do this is to set
{
...
"libraryTarget": "umd"
}
in your Webpack configuration. With that, Webpack knows that it should expose your entry module as a module that can be required in Webpack, but can also be loaded with a <script> tag as necessary.
Webpack libraryTarget documentation
The main problem i faced was to include the bundled js file of app1 after the DOM contains the target div it needs.
What i ended up doing was, creating a component in app2 project that will require() the bundled js file on componentDidMount() and will render and return the target div with a relevant id.
The reason i created a component is purely for re-usability purpose, instead of requiring this script with componentDidMount() on every component that needs it.
So, this is the component:
import React from 'react';
class AppOne extends React.Component {
componentDidMount() {
require('../app1/app1Bundle.js');
}
render() {
return (
<div id="app1"></div>
);
}
}
export default AppOne;
And this is how i use it in other component:
import React from 'react';
import AppOne from './AppOne';
const HomePage = () => {
return (
<div>
<h1>App 2 - wrapper for app1</h1>
<hr />
<AppOne />
<hr />
<h1>This is App2 as well </h1>
</div>
);
};
export default HomePage;
It's working fine. my only concern is that i may face some conflicts with react because i'm using 2 react apps though for ow i don't see any errors.
I guess that's an issue for a different question.
EDIT:
If someone will use this approach you should note that this will work only for the first load. because after the component will re-render itself the bundled script will not run again.

Categories