I am working on documentation tool for Typescript library. The idea is to leverage parcel's watch mode to continuously build the library, and use the same in a pre-built documentation app.
For the same I need to load a module library (built in another project) dynamically via URL.
<script type="module">
const libraryModule = "http://localhost:8080/lib.module.js";
const promise = import(libraryModule);
promise.then(library => {
// do something with library
window.ComponentLibrary = library;
});
</script>
However, parcel replaces the above import with require and the load fails. Using System.import throws System is not defined error.
I tried to use dynamic-import-polyfill and then initialize it as under and the use as below:
dynamicImportPolyfill.initialize({
modulePath: 'http://localhost:13090', // Defaults to '.'
importFunctionName: '$$import' // Defaults to '__import__'
const promise = $$import(libPath);
});
This throws the following error:
TypeError: Failed to resolve module specifier "react/jsx-dev-runtime". Relative references must start with either "/", "./", or "../"
I have also tried using script type as text/javascript but doesn't work either.
Looking for guidance on the best way here to get the component library loaded?
Figured it out: yes, we can load a component library as a module dynamically.
The issue was that React UMD module is not a pure ES/Javascript module. Also, with React 17, JSX components are picked from react/jsx-runtime. So, first I had to convert the React UMD module into an ES module - it's just a thin wrapper. Similarly, added a wrapper for jsx-runtime. To make things work had to use importmaps which are currently not supported in all browsers - see caniuse.com to check latest support.
This completes your setup and now your library compiled as ES module will work just fine. Below is what I used to get working:
<script type="importmap">
{
"imports": {
"react/jsx-runtime": "/react-jsx-runtime.js",
"react": "/react-as-es-module.js"
}
}
</script>
<script type="module" src="/component-library.js"></script>
<script type="module">
import * as MyComponentLib from "/component-library.js";
window.ComponentLibrary = { ...MyComponentLib };
</script>
Code for react-jsx-runtime.js looks as under:
import * as React from 'react';
export const jsx = React.createElement;
export const jsxs = React.createElement;
Code for react-as-es-module.js goes as:
import 'https://unpkg.com/react#17.0.2/umd/react.production.min.js';
const {
Children,
Component,
Fragment,
// and all other exports
} = React || {};
export {
Children,
Component,
Fragment,
// and all other exports
}
export default React;
I compiled component-library.js using ParcelJS using the type: "module" in package.json file. I would detail this in blog post and demo Github repo soon.
Hope this helps.
We have 2 configuration files: one is in our Spring Boot application (application.properties) and another in our ReactJs app (we use .env in create-react-app). It was decided to use Spring Boot application.properties also in ReactJs app. Can anyone please guide me how can I achieve this?
I have read about "properties-reader" and tried to use it, but I don't have webpack.config.js file in our ReactJs app.
Thymeleaf provides the easiest way to pass data from application.properties file to Javascript via the template (index.html) file.
Alternatively, it can be done using normal JSP also.
Here are the working examples:
Option 1: Thymeleaf
Step 1: Define the interesting data attributes as hidden elements in the index.html file
<div id="root"></div> ---> Div to be updated by react
....
<span id="myData" hidden></span>
<!-- Script to set variables through Thymeleaf -->
<script th:inline="javascript">
var myData = "[${myData}]]";
document.getElementById("myData").innerHTML = myData;
</script>
Important note:
Make sure that the same index.html file exists in the '/public' folder of Reactjs project as well as in the /src/main/resources/templates/ folder of the spring boot project.
Step 2: Use model.addAttribute() method in Spring Boot to invoke Thymeleaf for setting data in the index.html file
#GetMapping("/")
public String index(Model model) {
// Get the value of my.data from application.properties file
#Value("${my.data}")
private String myData;
// Call Thymeleaf to set the data value in the .html file
model.addAttribute("myData", myData);
return "index";
}
Step 3: Update the ReactJS code to read the interesting attribute using document.getElementById
let myData = document.getElementById("myData").innerHTML
More information:
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#javascript-inlining
https://attacomsian.com/blog/thymeleaf-set-javascript-variable
Option 2: JSP
Step 1: Define the interesting data attributes as hidden elements in the index.jsp file
Location of index.jsp: src/main/webapp/WEB-INF/jsp/index.jsp
<!DOCTYPE html>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html lang="en">
<head>
<!-- Head section here -->
</head>
<body>
<!-- Div to be updated by react -->
<div id="root">
</div>
<!-- Include the interesting attribute as a hidden field -->
<span id="myData" hidden>${myData}</span>
</body>
</html>
Important note:
Make sure that the /public/index.html file of reactjs project has the same content (<body>...</body>) as that of the src/main/webapp/WEB-INF/jsp/index.jsp file of spring boot project)
Step 2: Use map.put() in Spring Boot controller to update data in JSP
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class HomePageController{
// Read data from application.properties
#Value("${my.data}")
private String myData;
// Update data attributes of JSP using map.put
#GetMapping("/")
public String index( Map<String, Object> map ) {
map.put("myData", myData);
return "index";
}
}
Step 3: Update the ReactJS code to read the interesting attribute using document.getElementById
let myData = document.getElementById("myData").innerHTML
More information:
https://mkyong.com/spring-boot/spring-boot-hello-world-example-jsp/
There are three create-react-apps customised using react-app-rewired, both are using the same version of the following packages:
"react": "^16.12.0",
"react-app-rewired": "^2.1.5",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
App 1, the "Main" application is a very simple shell for the other two apps, public/index.html kind of looks like so:
<body>
<div class="main-app"></div>
<div class="sub-app-1"></div>
<div class="sub-app-2"></div>
<script src="sub-app-1/static/js/bundle.js" async></script>
<script src="sub-app-1/static/js/0.chunk.js" async></script>
<script src="sub-app-1/static/js/main.chunk.js" async></script>
<script src="sub-app-2/static/js/bundle.js" async></script>
<script src="sub-app-2/static/js/0.chunk.js" async></script>
<script src="sub-app-2/static/js/main.chunk.js" async></script>
</body>
This works well enough, all three apps are rendered correctly. Now the requirements have changed slightly where the one and only component from sub-app-2 for example <PictureFrame /> needs to be included in sub-app-1, I have created a stub component in the main project, like so:
const NullPictureFrame = () => {
return <div></div>;
}
export const PictureFrame = (props: Props) => {
const forceUpdate = useForceUpdate();
useEventListener(window, "PictureFrameComponent.Initialized" as string, () => forceUpdate());
const PictureFrame = window.PictureFrameComponent || NullPictureFrame;
return <PictureFrame />
}
I don't think the details of the hooks matter, but they do work when run in a stand alone fashion. The sub-app-2 does something similar to this
window.PictureFrameComponent = () => <PictureFrame />
which seems to work in theory, but in practice I end up with the following error
The above error occurred in the <PictureFrame> component:
in PictureFrame (at src/index.tsx:17)
in Router (at src/index.tsx:16)
in Unknown (at PictureFrames/index.tsx:21)
in PictureFrame (at src/index.tsx:32) <--- in `main-app`
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/docs/error-boundaries.html to learn more about error boundaries. index.js:1
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
main-app and sub-app-2 are loading two different versions of react and this is causing an issue when using hooks.
I have tried to update my webpack config based on the advice in this github thread but it appears that sub-app-2 cannot find the reference to react using this approach. My main-app/config-overrides.js looks like so:
config.resolve.alias["react"] = path.resolve(__dirname, "./node_modules/react");
config.resolve.alias["react-dom"] = path.resolve(__dirname, "./node_modules/react-dom");
and my sub-app-1/config-overrides.js looks like so:
config.externals = config.externals || {};
config.externals.react = {
root: "React",
commonjs2: "react",
commonjs: "react",
amd: "react"
};
config.externals["react-dom"] = {
root: "ReactDOM",
commonjs2: "react-dom",
commonjs: "react-dom",
amd: "react-dom"
};
Which results in no errors, but also the code from sub-app-2 not being initialised by webpack as react/react-dom cannot be found
Edit 1: Clarification
the 3 apps in question are 3 completley seperate (git) projects, just all using the same dependencies.
The only "dependency" permitted between the projects is this very loose coupling by using globals, Events, Window.postMessage or similar.
My initial approach of const PictureFrame = window.PictureFrameComponent || NullPictureFrame; works fine without using hooks, which the teams have been using until now, which has reinforced the idea of very lose dependencies detailed above
There is the "obvious" way of making "react", "react-dom" and anything that dependends on them part of the externals in the main-app and loading them in that way
I see you have taken an approach to have multiple projects under one directory/repository/codebase, which I think it is not the best approach. You should split and have all your projects independently, a library sub-project where you have all the shared code/components and then make each project to have access to what it needs.
You can read an article I have wrote about Share React Native components to multiple projects, which I suggest 3 ways for sharing code between projects. I'll emphasize to the Webpack/Babel way, which you can utilize module-resolver package to configure directory aliases. You can use .babelrc and module-resolver to configure your outer dependencies like this:
{
"presets": ["#babel/preset-react"]
"plugins": [
"#babel/plugin-transform-runtime",
[
"module-resolver",
{
"root": ["./"],
"extensions": [".js", ".jsx", ".ts", ".tsx"],
"stripExtensions": [".js", ".jsx", ".ts", ".tsx"],
"alias": {
"#components": "./components",
"#screens": "./screens",
"#utils": "./utils",
"#data": "./data",
"#assets": "./assets",
"#app": "./",
"#myprojectlib": "../MyProject-Library",
"#myprojecti18n": "../MyProject-Library/i18n/rn",
"#myprojectbackend": "../MyProject-Backend/firebase/functions",
}
}
]
]
}
After that, you can use something like:
import { PictureFrame } from '#myprojectlib/components/PictureFrame';
and you'll have it available on each project you have configured to have access to "#myprojectlib": "../MyProject-Library".
With this way, you can have multiple versions of React and any package inside your project because each project will be a standalone codebase with it's own dependencies but with the same shared components/functions.
As quoted in React.js documentation,
React Hooks:
Hooks are essentially built to access state and other react features in a classless component.
Rules to keep in mind while working with hooks
Only Call Hooks at the Top Level
Only Call Hooks from React Functions
From the details that you've posted, I believe you've already taken into consideration the above points.
For root cause analysis you probably need to set some debugging statements in your code or log messages on the console to investigate the component lifecycle.
Also, it'll be a lot better if you could reconstruct/rearrange the application as follows:
public/index.html
<body>
<div id="root" class="main-app"></div>
<script>
<!-- scrpits can be kept here -->
</script>
</body>
Create another component Main App and include sub-app-1 and sub-app-2 there.
and since you're using "react-dom": "^16.12.0" and "react-router-dom": "^5.1.2",
it'll be easier for your set the routes.
Can't really say this will solve your case, but it's a good practice to include.
All the best.
I have been faced the same issue in my project, the solution is simple.
you can go to all your subproject(s) and run bellow command.
npm link ../main_app/node_modules/react
Referece: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react
Partial Solution: Use the expose-loader webpack plugin
Using the expose-loader in the main-app allows both sub-app-1 and sub-app-2 to use the libraries from main-app
sub-app-1 and sub-app-2 have a config-overrides.js to indicate they rely on external libraries
module.exports = function override(config, env) {
config.externals = config.externals || {};
config.externals.axios = "axios";
config.externals.react = "React";
config.externals["react-dom"] = "ReactDOM";
config.externals["react-router"] = "ReactRouterDOM";
return config;
}
main-app is responsible for providing those libraries. This can be achieved either by embedding the libraries using <script /> tags or by using the expose-loader, the config-overrides.js effectively looks like so:
module.exports = function override(config, env) {
config.module.rules.unshift({
test: require.resolve("react"),
use: [
{
loader: "expose-loader",
options: "React"
}
]
});
config.module.rules.unshift({
test: require.resolve("react-dom"),
use: [
{
loader: "expose-loader",
options: "ReactDOM"
}
]
});
config.module.rules.unshift({
test: /\/react-router-dom\//, // require.resolve("react-router-dom"), did not work
use: [
{
loader: "expose-loader",
options: "ReactRouterDOM"
}
]
});
return config;
};
Now when I visit my main-app page, I can see React, ReactDOM and ReactRouterDOM on the window object. Now it is possible for my apps to load out of order, but now we need to ensure that the main-app is loaded first, fortunately there is a somewhat straightfoward way. In public/index.html I remove my referenced sub-apps
<body>
<div class="main-app"></div>
<div class="sub-app-1"></div>
<div class="sub-app-2"></div>
- <script src="sub-app-1/static/js/bundle.js" async></script>
- <script src="sub-app-1/static/js/0.chunk.js" async></script>
- <script src="sub-app-1/static/js/main.chunk.js" async></script>
- <script src="sub-app-2/static/js/bundle.js" async></script>
- <script src="sub-app-2/static/js/0.chunk.js" async></script>
- <script src="sub-app-2/static/js/main.chunk.js" async></script>
</body>
and in my index.tsx I can dynamically load the scripts in question and wait for them
// Will dynamically add the requested scripts to the page
function load(url: string) {
return new Promise(function(resolve, reject) {
var script = document.createElement("script");
script.type = "text/javascript";
script.async = true;
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// Load Sub App 1
Promise.all([
load(`${process.env.SUB_APP_1_URL}/static/js/bundle.js`),
load(`${process.env.SUB_APP_1_URL}/static/js/0.chunk.js`),
load(`${process.env.SUB_APP_1_URL}/static/js/main.chunk.js`)
]).then(() => {
console.log("Sub App 1 has loaded");
window.dispatchEvent(new Event("SubAppOne.Initialized"));
const SubAppOne = window.SubAppOne;
ReactDOM.render(<SubAppOne />, document.querySelector(".sub-app-1"))
});
// Load Sub App 2
Promise.all([
load(`${process.env.SUB_APP_2_URL}/static/js/bundle.js`),
load(`${process.env.SUB_APP_2_URL}/static/js/0.chunk.js`),
load(`${process.env.SUB_APP_2_URL}/static/js/main.chunk.js`)
]).then(() => {
console.log("Sub App 2 has loaded");
window.dispatchEvent(new Event("PictureFrameComponent.Initialized")); // Kicks off the rerender of the Stub Component
window.dispatchEvent(new Event("SubAppTwo.Initialized"));
const SubAppTwo = window.SubAppTwo;
ReactDOM.render(<SubAppTwo />, document.querySelector(".sub-app-2"))
});
Now main-app has all the shared dependencies like React, and will load the two sub-apps when the dependencies are ready. The PictureFrameComponent now works as expected with hooks! It would be preferrable if the loading could happen in any order, i.e. sub-app-1 could be loaded before main-app, however given the importance of the main-app and the minor addition in functionality provided by the PictureFrameComponent this could be a passable solution for some projects.
Has any one of you successfully integrate Moengage in ReactJs ? I have try it by put this inside <head> tag on index.html of HTMLWebpackPlugin's template.
<script type="text/javascript">
(function(i,s,o,g,r,a,m,n){
i['moengage_object']=r;t={}; q = function(f){return function(){(i['moengage_q']=i['moengage_q']||[]).push({f:f,a:arguments});};};
f = ['track_event','add_user_attribute','add_first_name','add_last_name','add_email','add_mobile',
'add_user_name','add_gender','add_birthday','destroy_session','add_unique_user_id','moe_events','call_web_push','track','location_type_attribute'];
for(k in f){t[f[k]]=q(f[k]);}
a=s.createElement(o);m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m);
i['moe']=i['moe'] || function(){n=arguments[0];return t;}; a.onload=function(){if(n){i[r] = moe(n);}};
})(window,document,'script','https://cdn.moengage.com/webpush/moe_webSdk.min.latest.js','Moengage');
</script>
I put the moengage's config in a file called 'moengage.js'. So i can easily import & use it in another files.
export const Moengage = moe({
app_id:"APP ID",
debug_logs: 0
});
Then, i use it in another file
import { Moengage } from '../config/moengage.js'
...
Moengage.track_event('Loan_Data', {
'loan_name': 'Example name',
'loan_type_id': 123,
})
Unfortunately, mine doesn't work. Did you ever try moengage on ReactJs ? Any help would be great. Thank you
The variable moe is available in the window and thus putting it in a config file will not work. The initialization script has to be placed in the <head> tag.
You can access the moe variable through the window wherever required. In your config file you can try something like this:
export const Moengage = window.moe({
app_id:"APP ID",
debug_logs: 0
});
PS. I develop the Web SDK at MoEngage. Please feel free to reach out to us at support#moengage.com for any further queries.
I created a React/Typescript project with dotnet new "ASP.NET Core with React.js".
index.cshtml:
<div id="react-app"></div>
#section scripts {
<script src="~/dist/main.js" asp-append-version="true">
</script>
}
boot.tsx(shortened):
function renderApp() {
ReactDOM.render(
<AppContainer>
<BrowserRouter children={ routes } />
</AppContainer>,
document.getElementById('react-app')
);
}
renderApp();
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}
How can I pass ASP.Core generated information(the routes from the controllers) to my react/typescript code?
To use server-side rendering in your application, follow the following steps:
1 - Modify App_Start\ReactConfig.cs (for ASP.NET MVC 4 or 5) or Startup.cs (for ASP.NET Core) to reference your components:
namespace MyApp
{
public static class ReactConfig
{
public static void Configure()
{
ReactSiteConfiguration.Configuration = new ReactSiteConfiguration()
.AddScript("~/Scripts/HelloWorld.jsx");
}
}
}
This tells ReactJS.NET to load all the relevant JavaScript files server-side. The JavaScript files of all the components you want to load and all their dependencies should be included here.
2 - In your ASP.NET MVC view, call Html.React to render a component server-side, passing it the name of the component, and any required props.
#Html.React("HelloWorld", new
{
name = "Daniel"
})
3 - Call Html.ReactInitJavaScript at the bottom of the page (just above the ) to render initialisation scripts. Note that this does not load the JavaScript files for your components, it only renders the initialisation code.
<!-- Load all your scripts normally before calling ReactInitJavaScript -->
<!-- Assumes minification/combination is configured as per previous section -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
#Scripts.Render("~/bundles/main")
#Html.ReactInitJavaScript()
4 - Hit the page and admire the server-rendered beauty:
<div id="react1">
<div data-reactid=".2aubxk2hwsu" data-react-checksum="-1025167618">
<span data-reactid=".2aubxk2hwsu.0">Hello </span>
<span data-reactid=".2aubxk2hwsu.1">Daniel</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
<script src="/Scripts/HelloWorld.js"></script>
<script>ReactDOM.render(HelloWorld({"name":"Daniel"}), document.getElementById("react1"));</script>
The server-rendered HTML will automatically be reused by React client-side, meaning your initial render will be super fast.
If you encounter any errors with the JavaScript, you may want to temporarily disable server-side rendering in order to debug your components in your browser. You can do this by calling DisableServerSideRendering() in your ReactJS.NET config.
For a more in-depth example, take a look at the included sample application (React.Samples.Mvc4).
5 - Server-side only rendering
If there is no need to have a React application client side and you just want to use the server side rendering but without the React specific data attributes, call Html.React and pass serverOnly parameter as true.
#Html.React("HelloWorld", new
{
name = "Daniel"
}, serverOnly: true)
And the HTML will look like the one following which is a lot cleaner. In this case there is no need to load the React script or call the Html.ReactInitJavaScript() method.
<div id="react1">
<div>
<span>Hello </span>
<span>Daniel</span>
</div>
</div>