So basically I want to render one single React ponent (a notification ponent) into my Angular project. I created Notification.tsx
import * as React from 'react';
import { FunctionComponent, useEffect, useRef, useState } from 'react';
import { IconButton, Badge, Menu, List, ListItem, ListItemIcon, ListItemText, ListItemSecondaryAction, Avatar } from '@material-ui/core';
import { Notifications, Delete, Event, HourglassEmpty, Alarm } from '@material-ui/icons';
import { makeStyles } from '@material-ui/core/styles';
import { grey } from '@material-ui/core/colors';
export interface NotificationProps { }
export const NotificationComponent: FunctionComponent<NotificationProps> = (props: NotificationProps) => {
return <div className={"row"}>
<IconButton disableRipple={true} aria-label="notification" className="py-3 my-auto"
onClick={handleNotificationMenuClick} aria-controls="notification-drop-down-menu"
aria-haspopup="true">
<Badge badgeContent={4} max={99} color="secondary">
<Notifications />
</Badge>
</IconButton>
</div>
};
A wrapper ponent NotificationWrapper.tsx
import { AfterViewInit, Component, ElementRef, OnChanges, OnDestroy, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { NotificationComponent } from './Notification';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const containerElementName = 'notificationComponentTemplate';
@Component({
selector: 'react-notification-ponent',
template: `<span #${containerElementName}></span>`,
encapsulation: ViewEncapsulation.None,
})
export class NotificationWrapper implements OnChanges, OnDestroy, AfterViewInit {
@ViewChild(containerElementName, { static: false }) containerRef: ElementRef;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
this.render();
}
ngAfterViewInit() {
this.render();
}
ngOnDestroy() {
ReactDOM.unmountComponentAtNode(this.containerRef.nativeElement);
}
private render() {
ReactDOM.render(<div className={'notification-wrapper'}>
<NotificationComponent />
</div>, this.containerRef.nativeElement);
}
}
Added this wrapper to app.module.ts
's @NgModule
import { NotificationWrapper } from "../react-ponents/notification/NotificationWrapper";
@NgModule({
declarations: [
NotificationWrapper,
],
})
Used the notification wrapper selector as follows:
<div class="col-sm-6">
<react-notification-ponent></react-notification-ponent>
</div>
Everything works fine when served locally and as I went through other similar questions on this site: I've added "jsx": "react"
to tsconfig.json
,
plugins: [
new webpack.ProvidePlugin({
"React": "react",
})
]
and
externals: {
'react': 'React'
},
to webpack.config.js
. Here's the whole file for reference.
// Work around for
const path = require('path');
const webpack = require('webpack');
// change the regex to include the packages you want to exclude
const regex = /firebase\/(app|firestore)/;
module.exports = {
mode: 'production',
entry: {
// This is our Express server for Dynamic universal
server: './server.ts'
},
externals: {
'./dist/server/main': 'require("./server/main")',
'react': 'React'
},
target: 'node',
node: {
__dirname: false,
__filename: false,
},
resolve: { extensions: ['.ts', '.js'] },
target: 'node',
mode: 'none',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/node_modules/, function (context, request, callback) {
// exclude firebase products from being bundled, so they will be loaded using require() at runtime.
if (regex.test(request)) {
return callback(null, 'monjs ' + request);
}
callback();
}],
optimization: {
minimize: false
},
output: {
// Puts the output at the root of the dist folder
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
noParse: /polyfills-.*\.js/,
rules: [
{ test: /\.ts$/, loader: 'ts-loader' },
{
// Mark files inside `@angular/core` as using SystemJS style dynamic imports.
// Removing this will cause deprecation warnings to appear.
test: /(\\|\/)@angular(\\|\/)core(\\|\/).+\.js$/,
parser: { system: true },
},
]
},
plugins: [
new webpack.ProvidePlugin({
"React": "react",
}),
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
};
My problem is, when I build my app, the notification ponent is not rendered and I get this error in console ERROR ReferenceError: React is not defined
. Is there anything that I missed? Thanks in advance.
So basically I want to render one single React ponent (a notification ponent) into my Angular project. I created Notification.tsx
import * as React from 'react';
import { FunctionComponent, useEffect, useRef, useState } from 'react';
import { IconButton, Badge, Menu, List, ListItem, ListItemIcon, ListItemText, ListItemSecondaryAction, Avatar } from '@material-ui/core';
import { Notifications, Delete, Event, HourglassEmpty, Alarm } from '@material-ui/icons';
import { makeStyles } from '@material-ui/core/styles';
import { grey } from '@material-ui/core/colors';
export interface NotificationProps { }
export const NotificationComponent: FunctionComponent<NotificationProps> = (props: NotificationProps) => {
return <div className={"row"}>
<IconButton disableRipple={true} aria-label="notification" className="py-3 my-auto"
onClick={handleNotificationMenuClick} aria-controls="notification-drop-down-menu"
aria-haspopup="true">
<Badge badgeContent={4} max={99} color="secondary">
<Notifications />
</Badge>
</IconButton>
</div>
};
A wrapper ponent NotificationWrapper.tsx
import { AfterViewInit, Component, ElementRef, OnChanges, OnDestroy, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { NotificationComponent } from './Notification';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const containerElementName = 'notificationComponentTemplate';
@Component({
selector: 'react-notification-ponent',
template: `<span #${containerElementName}></span>`,
encapsulation: ViewEncapsulation.None,
})
export class NotificationWrapper implements OnChanges, OnDestroy, AfterViewInit {
@ViewChild(containerElementName, { static: false }) containerRef: ElementRef;
constructor() { }
ngOnChanges(changes: SimpleChanges): void {
this.render();
}
ngAfterViewInit() {
this.render();
}
ngOnDestroy() {
ReactDOM.unmountComponentAtNode(this.containerRef.nativeElement);
}
private render() {
ReactDOM.render(<div className={'notification-wrapper'}>
<NotificationComponent />
</div>, this.containerRef.nativeElement);
}
}
Added this wrapper to app.module.ts
's @NgModule
import { NotificationWrapper } from "../react-ponents/notification/NotificationWrapper";
@NgModule({
declarations: [
NotificationWrapper,
],
})
Used the notification wrapper selector as follows:
<div class="col-sm-6">
<react-notification-ponent></react-notification-ponent>
</div>
Everything works fine when served locally and as I went through other similar questions on this site: I've added "jsx": "react"
to tsconfig.json
,
plugins: [
new webpack.ProvidePlugin({
"React": "react",
})
]
and
externals: {
'react': 'React'
},
to webpack.config.js
. Here's the whole file for reference.
// Work around for https://github./angular/angular-cli/issues/7200
const path = require('path');
const webpack = require('webpack');
// change the regex to include the packages you want to exclude
const regex = /firebase\/(app|firestore)/;
module.exports = {
mode: 'production',
entry: {
// This is our Express server for Dynamic universal
server: './server.ts'
},
externals: {
'./dist/server/main': 'require("./server/main")',
'react': 'React'
},
target: 'node',
node: {
__dirname: false,
__filename: false,
},
resolve: { extensions: ['.ts', '.js'] },
target: 'node',
mode: 'none',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/node_modules/, function (context, request, callback) {
// exclude firebase products from being bundled, so they will be loaded using require() at runtime.
if (regex.test(request)) {
return callback(null, 'monjs ' + request);
}
callback();
}],
optimization: {
minimize: false
},
output: {
// Puts the output at the root of the dist folder
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
noParse: /polyfills-.*\.js/,
rules: [
{ test: /\.ts$/, loader: 'ts-loader' },
{
// Mark files inside `@angular/core` as using SystemJS style dynamic imports.
// Removing this will cause deprecation warnings to appear.
test: /(\\|\/)@angular(\\|\/)core(\\|\/).+\.js$/,
parser: { system: true },
},
]
},
plugins: [
new webpack.ProvidePlugin({
"React": "react",
}),
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
// fixes WARNING Critical dependency: the request of a dependency is an expression
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
};
My problem is, when I build my app, the notification ponent is not rendered and I get this error in console ERROR ReferenceError: React is not defined
. Is there anything that I missed? Thanks in advance.
2 Answers
Reset to default 7I think it's a typical unwanted optimization that remove React in the final js (React isn't directly used)
Try to call a method or an attribute on React like React.version :
import {
OnChanges,
ViewChild
} from '@angular/core';
import { MyComponent } from './react/MyComponent';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const containerElementName = 'myReactComponentContainer';
@Component({
...
})
export class MyComponent implements OnChanges {
@ViewChild(containerElementName) containerRef: ElementRef;
ngOnChanges(changes: SimpleChanges): void {
this.render();
}
private render() {
React.version;
ReactDOM.render(<MyReactComponent />, this.containerRef.nativeElement);
}
}
Add the following to tsconfig.json
"pilerOptions": {
"allowSyntheticDefaultImports": true,
"jsx": "react",
}
Set aot
and buildOptimizer
to false in angular.json
Replace
import * as React from 'react';
import * as ReactDOM from 'react-dom'
with
import React from 'react';
import ReactDOM from 'react-dom';