Rendering problem with React app inside Docker container - reactjs

The React app described below is built in VS Code with React v18.0.0, node v.14.15.4 and npm v8.4.1. It also imports modules from a 3rd party mapping API called the ArcGIS JavaScript API and I'm running v4.18 of that.
I've used this mapping API to create many vanilla JS web apps over the years. I reference its JS and CSS via CDN. The current need is to leverage it within a React app that's deployed in a Docker container running NGINX.
I can launch the app from VS Code using 'npm start'. It runs at localhost:3000 and everything works as expected. The map renders within the correct div and it shows the gray canvas basemap tiles. The CSS is also loading correctly and the zoom +/- buttons render correctly. All of this correct behavior happens when the app is launched locally. I can deploy it IIS or I can just run 'npm start' in VS Code and it runs without issues. This is how it displays:
Code within the React app that creates the map and map view:
import logo from './logo.svg';
import './App.css';
import { useEffect, useState, useRef } from "react";
import esriConfig from "#arcgis/core/config";
import Map from "#arcgis/core/Map";
import MapView from "#arcgis/core/views/MapView";
import FeatureLayer from "#arcgis/core/layers/FeatureLayer";
function App() {
const mapRef = useRef();
esriConfig.apiKey = "REMOVED";
const initMap = () => {
const map = new Map({
basemap: "arcgis-light-gray",
layers: []
});
const view = new MapView({
map: map,
center: [-82.354, 27.895],
zoom: 9,
container: mapRef.current,
});
};
useEffect(() => {
initMap();
}, []);
return (
<div className="App">
<div id="map-div" ref={mapRef}>
</div>
</div>
);
}
export default App;
All of the above works when hosted locally or builT and deployed to IIS.
But the ultimate goal is to deploy this app within a Docker container running NGINX. The docker image builds successfully and also runs successfully, but when I browse the app running inside the container the basemap tiles don't render. CSS and JS are both being loaded, I get 200 responses for both. The 3rd party content is loading but for some reason the images tiles in the map don't render and all I get is a map with a blank background.
I get the same result in Chrome and Edge. There are no errors or exception thrown in the browser's console. It's as if the JS API is loading successfully but somehow the image tiles aren't rendering and I can't see any reason why this would fail only inside a Docker container.
Dockerfile
FROM node:latest as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:latest
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build/ /usr/share/nginx/html/
.dockerignore file
node_modules
Dockerfile
.git
default.conf for NGINX
server {
listen 80;
listen [::]:80;
root /usr/share/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name localhost;
location / {
try_files $uri $uri/ =404;
}
}
Because I have other React apps with ArcGIS JS maps working perfectly, I believe the problem has to have something to do with Docker and/or NGINXand how it is failing to handle the image tiles correctly.
I also noticed some differences in the requests being made by each version of this app. In the network tab of Chrome's developer tools it looks like the app makes an extra call when running locally that is not made when it's running in a docker container.
Locally it makes two calls to LightGray:Base and LightGray:Labels, and also World_Basemap_v2 further down.
I see fewer calls for the light gray basemap resource in the Docker container and no request for World_Basemap_v2 at all.
Unfortunately I don't know if the above differences are significant at all.
Has anyone else had any issues along these lines? I've already called for help on the ArcGIS forum but I sense that almost nobody is trying to run this API within Docker.
Thanks
Update & Work-Around
Uninstalling #arcgis/core and installing esri-loader instead resulted in correct rendering of the basemap I'm using.
import logo from "./logo.svg";
import "./App.css";
import { loadModules } from "esri-loader";
function App() {
loadModules(['esri/config', 'esri/views/MapView', 'esri/Map']).then(([esriConfig, MapView, Map]) => {
esriConfig.apiKey = "REMOVED";
const map = new Map({
basemap: 'arcgis-light-gray'
});
const mapView = new MapView({
map: map,
zoom: 10,
center: [-82.5, 28],
container: "map-div"
});
});
return (
<div className="App">
<div id="map-div"></div>
</div>
);
}
export default App;

I found two work-arounds.
The problem seems to be with the esriConfig module, specifically defining an API key within a React app. A key is required to use the premium set of basemaps which includes the one I'm using, "arcgis-light-gray". I have always defined my developer API key in my ArcGIS JS apps and I've done so in this React app as well. But this module appears to be broken in React. Either that or I need to reference the module differently than I currently am. If so, it's not covered in any documentation I could find.
Work-around #1 - use a non-premium basemap like "gray" or "gray-vector". These basemaps lack the same level of detail at higher zoom levels, making this approach unacceptable if you want to zoom in to larger scales and see detail on individual parcels.
Work-around #2 - uninstall #arcgis/core and install esri-loader instead. This set of modules appears to be the better way to use the ArcGIS JS API in React. With esri-loader the modules are imported with the loadModules function instead of require or import. After refactoring the app to use esri-loader I am now able to define my API key and the premium "arcgis-light-gray" basemap renders correctly. I'll put updated code in the original post.

Related

Fetch React components from server

I am working on ReactJs for a will. I used Typescript in both client and server side for development. I used npm run build to build my project, I will deliver only the build folder now.
Client:
Client
build
-static
-index.html
Server
node_modules
src
package.json
package-lock.json
tsconfig.json
everything is file, For few cases I need get ReactjsComponents from server. These new Components are created by each user.
New ReactJs
ButtonList
ButtonListComponent.tsx
index.ts
IProps.ts
I converted .tsx file to .js using babel
ButtonList
ButtonListComponent.d.ts
ButtonListComponent.js
index.d.ts
index.js
IProps.d.ts
IProps.js
And In server I rendered to plain html and sent the html as string to client and printed.
ReactHelper.ts
import React from 'react'
import * as ReactDOMServer from 'react-dom/server'
import ButtonList from '../../NewComponents/ButtonList'
export function ExportReactComponent(){
var reactHtml = ReactDOMServer.renderToStaticMarkup(ButtonList);
return reactHtml
}
But I used functional Components and materialUI the hooks are not working. So instead of create reactComponents and converting to js. I wrote Plain html and sent to client for printing. but in those case scripts are not working.
ReactHelper.ts
export function ExportReactComponent(){
var htmlInput = `<h1>The script element</h1>
<p id="demo"></p>
<script>
document.getElementById("demo").innerHTML = "Hello JavaScript!";
</script>
`;
return htmlInput
}
someone help me, if tried these things but not works as expected, is there any other good solution to this? Thanks in advance.
Git repo: https://github.com/nivedhauswin/ReactApp.git
I used Iframe to display user components. I want to track location change in Iframe contentWindow.location.href but i get
SecurityError: Blocked a frame with origin "http://localhost:3000" from accessing a cross-origin frame.

I can see my images with gatsby develop, but not on production site

I have my site's images stored in the src/images folder in my Gatsby project. I reference those files in <StaticImage> components after importing them from the images folder, like so:
Within index.js:
import myImage from '../images/flag.jpeg'
import { StaticImage } from "gatsby-plugin-image"
and later inside the same file:
<StaticImage className="flag" src={myImage} alt="flag"/>
I can see this image and others just fine when running the site locally (gatsby develop), but when I deploy the site to AWS activate, I do not see ANY images.
What could be going wrong? Could this be a React issue?
Also, I cannot see videos either, though I am not aware of a <StaticVideo/> component

Create react app - how to copy pdf.worker.js file from pdfjs-dist/build to your project's output folder?

Since I can't use browser's pdf viewer in the network where the app is going to be used, I am testing a react-pdf package for loading PDF's with React.
I have made a component where I am sending a url of my PDF that I get from backend:
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
const PDFViewer = ({url}) => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function onLoadError(error) {
console.log(error);
}
function onSourceError(error) {
console.log(error);
}
return (
<div>
<Document
file={window.location.origin + url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onLoadError}
onSourceError={onSourceError}
>
{[...Array(numPages).keys()].map((p) => (
<Page pageNumber={p + 1} />
))}
</Document>
</div>
);
};
export default PDFViewer;
But, on opening the PDFViewer I get an error
Error: Setting up fake worker failed: "Cannot read property 'WorkerMessageHandler' of undefined"
In documentation it says that you should set up service worker and that the recommended way is to do that with CDN:
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
But, I can't use CDN links for my project, and in the documentation it also says:
Create React App uses Webpack under the hood, but instructions for Webpack will not work. Standard instructions apply.
Standard (Browserify and others)
If you use Browserify or other bundling tools, you will have to make sure on your own that pdf.worker.js file from pdfjs-dist/build is copied to your project's output folder.
There are no instructions on how to do that with create-react-app. How can I set this up locally then?
Install pdfjs-dist
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
Reference: https://github.com/mozilla/pdf.js/issues/8305
found a more efficient way of including the worker
by including the library from the dependencies of react-pdf itself, this way you will never get a version mismatch like this The API version "2.3.45" does not match the Worker version "2.1.266"
if you install pdfjs-dist manually you will have to check react pdf dependency version on every build
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
see similar error on pdfjs library : https://github.com/mozilla/pdf.js/issues/10997
hope it helps people
You can install worker loader module for webpack:
npm install worker-loader --save-dev
Then use it inline where you are going to work with a worker:
import SomeWorker from 'worker-loader?inline=true!../workers/some.worker'
const someWorker: Worker = new SomeWorker()
someWorker.postMessage(...)
I haven't tried this solution with react-pdf, but it might help.
You may need to add types for TypeScript if you are using it:
declare module 'worker-loader*' {
class SomeWorker extends Worker {
constructor()
}
export default SomeWorker
}
Just to add that in some .d.ts file in your project.
Install pdfjs-dist then use the webpack module:
import { pdfjs } from 'react-pdf'
import worker from 'pdfjs-dist/webpack'
pdfjs.GlobalWorkerOptions.workerSrc = worker
If your build process uses cli commands, (i.e. AWS buildspec), you can use this:
mkdir -p build && cp ./node_modules/pdfjs-dist/build/pdf.worker.js build
If you are in a corporate codebase environment and have little to no experience configuring WebPack, I wanted to share a little more info if (like me) you struggled with this for quite a long time.
My environment has several complicated WebPack config files (base, production, and development), and the resolution ended up being pretty simple but it escaped me for quite a while because I was unfamiliar with the complicated build process.
1) The Implementation
Quite simple, just as the docs recommend (I went with the minified file). Our React environment required me to use React-PDF#4.2.0, but there aren't any differences here.
import {Document, Page, pdfjs} from 'react-pdf'
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js'
Note: a previous solution recommended grabbing the source from the react-pdf node_modules folder, however, my codebase is setup to install dependencies separately somehow because when I npm install react-pdf, pdfjs-dist is also installed separately. Regardless, this method did not work for my codebase (importing the worker as a variable) due to the way the project is built. The import command acted like it couldn't find the proper named export inside a node_modules folder. It was top-level or nothing.
2) WebPack Config
Since I do not know WebPack at all, but found pretty easily that what I needed to do was take advantage of CopyWebpackPlugin, I searched through those existing dev and prod webpack config files, and found existing copy commands for JQuery and polyfill and added a new plugin to that array:
new CopyWebpackPlugin({from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js})
I had to do this in multiples places in both config files as this large project has several entry point server files for the different services of the website.
3) Inserting Script Tag to HTML Head
This was the crucial part I was missing. There was a "ComponentFactory" file whose job it was to insert chunks of html in the <head> and tail of the html file. I wasn't used to something like this on small projects. So there, I simply copied what was already done for the jquery and polyfill, which included a string literal of the location of the assets folder the webpack was building out to. In my case, that was something like "assets/v1/". So the tag looked like this:
<script src=`${STATIC_ASSETS_URL}/pdf.worker.min.js` defer></script>
It works perfectly, however I am still getting the "Setting Up a Fake Worker" but immediately after that, it loaded it successfully in console and checking the dev tools, it was using the proper file. It's probably just a timing thing of the src set not running high enough in the code, but it was not effecting the outcome, so I let it go.
(Sidebar, if you also get the "TT unknown function" (paraphrasing) error, that can be ignored. It's just a font issue with whatever PDF you are loading and is just a warning, not an error.)
I was facing this issue once I had to use "react-pdf" from within a package.
It was solved by importing the worker conditionally into the code:
Conditional import:
export const getWorker = () => {
try {
return require('react-pdf/node_modules/pdfjs-dist/legacy/build/pdf.worker.entry.js')
} catch () {
return require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
}
}
usage:
import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'
pdfjs.GlobalWorkerOptions.workerSrc = getWorker()

Where should I put general app config in a Gatsby project?

I'm new to Gatsby, but familiar with CRA. I'm using the default Gatsby setup generated by running gatsby new <project_name> from Gatsby CLI.
I have some general config that I want run in my whole project, regardless of current page - for example:
import { enableMapSet, enableES5 } from "immer";
enableMapSet();
enableES5();
In CRA projects I put this stuff inside App.tsx, but can't figure out what the right place is in a Gatsby project.
In Gatsby, you don't have a single configuration file per se. There are several APIs exposed by Gatsby that allows you to configure one specific part of your site.
To run a set of functions regardless of the page you can use a gatsby-browser.js (placed in the root of the project).
Basically, Gatsby exposes a bunch of APIs that lets you respond to actions within the browser, and wrap your site in additional components. The Gatsby Browser API gives you many options for interacting with the client-side of Gatsby. One of them, onClientEntry fits your requirements. From its documentation:
onClientEntry
(_: emptyArg, pluginOptions: pluginOptions) => undefined
Called when the Gatsby browser runtime first starts.
Applied to your code, your gatsby-browser.js should look like:
import { enableMapSet, enableES5 } from "immer";
import React from 'react';
export const onClientEntry = () =>{
enableMapSet();
enableES5();
};
The snippet above will trigger enableMapSet() and enableES5() on every page.

How to integrate a react application in drupal 8, using drupal custom module?

I am very new to react and drupal 8. I know to create custom modules in drupal and react SPAs, but I m not able to call my react app using a drupal8 controller .
Can someone please make me clear of the flow and the correct way to integrate react app in drupal 8?
So there isn't really a good means of calling a React application from within the regular Drupal controller layer or in the twig templates of Drupal 8.
There are two ways people usually connect a React Application to D8.
Option 1 - Progressively decoupled sites - This is where Drupal still uses the TWIG engine to generate the vast majority of the site views, and can use React for some small part of the site while communicating with Drupal through a Drupal based webservice. Check out this project for more information - https://www.drupal.org/project/pdb. This is a nice option if you just want to add a small React based widget, but want to keep the bulk of your site in using standard TWIG.
Option 2 - Fully decouples sites - This is where you render 100% of your applications view layer using React, and just use Drupal as a CMS that provides a web service. There are multiple options for the webservice portion including https://www.drupal.org/project/graphql and https://www.drupal.org/docs/8/api/restful-web-services-api/restful-web-services-api-overview. So an example of this would be serving a create-react-app on a static server and communication with D8 through a web service.
Here is some additional information that might help guide your decision.
https://dri.es/how-to-decouple-drupal-in-2018
Best of luck!
Long post with some assumptions(but it works):
I was seeking to achieve the same (Drupal 8 and react decoupled block), and I searched and searched, I found myself returning to this page more than once, so I will leave the little thing I discovered here.
My Assumptions:
you have created a custom block that has it's own twig template.
you have defined your libraries in your libraries file (we will review this)
you have created your react app in the root folder of your module with npx create-react-app my-app.
create-react-app my-app creates a react app inside my-app folder, my-app contains all the react code and configs. To get our app(custom react js library) to play well with drupal we will need to override somethings, like scripts to rename our files (build command),to something drupal can identify(recognize) and load.
Run yarn add react-app-rewired --dev, to download react app rewire, that let's us override the default react-app configs without having to eject our app.
In the root of your react-app folder, create a file named config-overrides.js that should contain the below code
module.exports = function override(config, env) {
config.optimization.runtimeChunk = false;
config.optimization.splitChunks = {
cacheGroups: {
default: false
}
};
return config;
};
and edit the scripts in in the package.json to
"build": "react-app-rewired build && yarn run build:dist",
"build:dist": "cd build && copy static\\js\\*.js main.js && copy static\\css\\*.css main.css",
NB: I have edited the build command and added the build:dist script (however if not on windows please
replace copy with cp and \\ with / in the build:dist). This will make sure every time you run the build script, your build files will be renamed to main.js and main.css without the filename..js/css which we can then reference in our libraries.yml file.
my modulename.libraries.yml looks like this (filename = modern_js_drupal.libraries.yml)
react_local:
version: 1.x
js:
my-app/build/main.js: {}
css:
layout:
my-app/build/main.css: {}
and my block.html.twig
<div class="row">
<div id="root">
<h4>React App</h4>
</div>
{{ attach_library('modern_js_drupal/react_local') }}
The reason I named my div 'root' and not anything else if because react app uses the same id when rendering your app.
Look into react_js/public/index.html and react_js/src/index.js. index.html provides the div to hook our app into and index.js renders the app on the div provided ( ReactDOM.render(<App />, document.getElementById('root'));), The advantage of this during development is, you get to create your app and view the changes instantly on your app (http://localhost:3000/) and you can later run yarn build to view the most recent changes on your drupal 8 site.

Resources