ReactJS Render issue for Browser Extension Content Scripts on Chrome - reactjs

I have made a browser extension which injects an iframe into the DOM that has some html in it. I have been trying to render a React component into the iframe, but it won't render and I receive the following error Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I've tried rendering to both the iframe and even straight to the DOM via content scripts, but I still keep getting the same problem. I know the component's render function is being called, because I placed a console.log in it and it displays the message; I have also debugged it and the breakpoints stop in the render function. In addition I added the lifecycle methods componentWillUpdate and componentDidUpdate and they are not even called.
//Index.jsx
import React from "react";
import ReactDOM from "react-dom";
class Index extends React.Component{
constructor(props){
super(props);
}
componentWillUpdate(){
console.log('componentWillUpdate');
}
componentDidUpdate(){
console.log('componentDidUpdate');
}
render() {
console.log('render');
return <div>Hello!</div>;
}
}
ReactDOM.render(<Index/>, document.getElementById("g-root"))
// Including this code to show how I was adding the react component to the DOM if it is loaded immediately via manifest.json
// const app = document.createElement('div');
// app.id = "g-root";
// document.body.appendChild(app);
// ReactDOM.render(<Index />, app);
//webpack.config
const path = require('path');
module.exports = (env, args) => {
return {
mode,
devtool,
entry: {
react: path.join(__dirname, './Index.jsx')
},
module: {
rules: [{
test: /\.jsx$/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react', 'es2015']
}
}
}]
},
output: {
path: path.join(__dirname, './'),
filename: 'react.bundle.js'
}
}
};
<!--This gets loaded into the iframe-->
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="g-root"></div>
<script src="react/react.bundle.js"></script>
</body>
</html>
//manifest.json *included just to show that it has the same issue on page load.
{
"content_scripts":
[{
"js":["react/react.bundle.js"],
"run_at": "document_end",
"matches": ["<all_urls>"]
}]
}
It should be noted that injecting the iframe, and the extension as a whole, worked fine until I started trying to add React to the project.

Turns out it was an issue with an internal library having to do with rendering web components with my React components.

Related

Loading micro frontend root App component's CSS in singleSpaReact on mounting to the host application

colleagues!
Need a piece of advice! Micro frontend in singleSpaReact does not load root's component CSS at the mounting, but only page CSS according routing navigation.
Implemented a micro frontend using singleSpaReact. In webpack.config for devmode build use:
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
ignoreOrder: false,
}),
new ExposeRuntimeCssAssetsPlugin({
filename: '[name].[contenthash].css',
}),
],
We use CSS-modules.
Have been running into an issue on mounting our micro front end to the host application: it does not load root App component's CSS. Only loads CSS for a page it navigates to via routing. Our entry point looks like:
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err, info, props) {
return GlobalErrorBoundary
},
})
export const { bootstrap, mount, unmount } = lifecycles
So, the root React component is App. It renders a tree of components with some CSS modules in each one, but the CSS is not loaded. I checked how the CSS is applied in our host app: it adds "link" tag for pages's CSS into the head on navigation via routing.
What's wrong in our build, what extra steps might be added to address the issue, I.e. to make "App" root component's CSS loaded?
Highly appreciate any clue!
Already solved by means of adding singleSpaCss module.
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err, info, props) {
return GlobalErrorBoundary
},
})
const cssLifecycles =
!getIsDevelopment() &&
singleSpaCss({
// cssUrls: [],
webpackExtractedCss: true,
shouldUnmount: true,
timeout: 5000,
})
let bootstrap
let mount
let unmount
if (getIsDevelopment()) {
bootstrap = reactLifecycles.bootstrap
mount = reactLifecycles.mount
unmount = reactLifecycles.unmount
} else {
bootstrap = [cssLifecycles.bootstrap, reactLifecycles.bootstrap]
mount = [
// The css lifecycles should be before your framework's mount lifecycle,
// to avoid a Flicker of Unstyled Content (FOUC)
cssLifecycles.mount,
reactLifecycles.mount,
]
unmount = [
// The css lifecycles should be after your framework's unmount lifecycle,
// to avoid a Flicker of Unstyled Content (FOUC)
reactLifecycles.unmount,
cssLifecycles.unmount,
]
}
export { bootstrap, mount, unmount }

React SSR alternatives

I am currently working on a project that someone else built and I was asked to implement server side rendering, it's a huge project and it uses a custom routing system based on a "builder JSON" taken by a main component that selects which components to render based on the route, it is meant to keep the app dynamic and to adjust to the needs of several customers.
I have been checking everywhere trying to find an answer but im new to SSR and it's a big challenge.
I am currently testing an approach using express that looks like this:
import 'babel-polyfill';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router';
import bodyParser from 'body-parser';
import { App } from '../src/App';
const app = express();
const PORT = process.env.PORT || 8000;
app.use(bodyParser.json());
app.use(express.static('ssrBuild'))
app.get('*', (req, res) => {
const context = {}
const content = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
const html = `
<html>
<head>
</head>
<body>
<div id="root">
${content}
</div>
</body>
</html>
`;
res.send(html);
});
app.listen(PORT, () => {
console.log(`App running on port ${PORT}`);
});
The problem I am currently having is that the App component calls the "complex routing component" from another repository in the node_modules (also build by them), my webpack config is taking the App component and finding jsx, which cant obviouly be run in the server.
Webpack config:
const path = require('path');
const webpackNodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
entry: {
server: './ssr/server.js',
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '..', 'ssrBuild'),
publicPath: '/ssrBuild'
},
module: {
rules: [
{
test: /\js$/,
loader: 'babel-loader',
exclude: '/node_modules/',
options: {
presets: [
'#babel/react',
['#babel/preset-env', {
targets: { browsers: ['last 2 versions'] }
}]
],
plugins: [
['#babel/plugin-proposal-decorators', { 'legacy': true }],
'#babel/plugin-proposal-class-properties',
'#babel/plugin-syntax-function-bind',
'#babel/plugin-transform-async-to-generator',
'#babel/plugin-proposal-export-default-from',
'babel-plugin-jsx-control-statements',
'react-hot-loader/babel',
'lodash',
]
}
}
]
},
externals: [webpackNodeExternals()]
}
Is there a way I can tell webpack to transpile the jsx in the node_modules folder "on the go"? What other solutions could there be?
Also, the whole point of this project is to impprove the SEO for these apps, I would only need to SSR the index page and any direct link to content in it. No need to SSR the entire app is there a way this can be achieved?
I was also wondering if refactoring the entire app to use Next.js would be worth it if this were possible only for the index page and any direct link.
Thank you in advance!
If the team can afford it then you should definitely go and try a framework. This will me more maintainable on the long term. I would recommend you try Next.js over Gatsby, Both are great options but in my opinion Next has two or three advantages like Incremental Static Regeneration (Regenerate if your content change continuously) or you can choose between use Server Sider or Static generation based on your routes. You can use SSR on your dashboard and SSG on your home and landing pages for example.
If you don't need any type of pre-rendering you can go for client-side only and even then Next are gonna make a couple of optimization that will speed up your site.
On the long term it will save you a lot of time and it will be easy to maintain

Viewing an image inside img tag using webpack in react is returning an object%20 module

I am passing an image name (this.props.backgroundImg) as a prop to a component (which points to "justice.jpg"). In the recipient component, i am appending the relative path using template literal to the src attribute in an img tag. So far so good.
When the page is rendered by the browser, there is no error but in Network chrome browser tool, i see an Object%20module instead of the image file.
The filename being constructed using the relative path and template string literal does point to the image file as there is no 404 file not found error.
Please let me know what i am missing here with the right explanation for better understanding?
The image does get rendered on the browser when i import the file "landingPageBkg"
The Component code is following.
import React from 'react';
//import landingPageBkg from '../../../public/assets/justice.jpg'
class LandingPage extends React.Component {
componentDidMount() {
setTimeout(()=>{
this.props.history.push('/pagenotfound');
},30000)
}
render() {
console.log(typeof `../../../public/assets/${this.props.backgroundImg}`)
return (
<div className="landingPageImg">
<img src={require(`../../../public/assets/${this.props.backgroundImg}`)} className="landingPageImg"/>
</div>
);
}
}
export default LandingPage;
Webpack url loader config:-
module: {
rules: [
{
loader: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
},
{
test: /\.s?css$/,
use: ['style-loader',
'css-loader', 'sass-loader'],
},
{
test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg|jpg)(\?[a-z0-9=.]+)?$/,
loader: 'url-loader',
},
],
You are getting an image via props (which will run in production mode). Webpack has no idea of what’s gonna happens after the build(therefore, won’t be able to determine the name/chunk of the image you’re getting). You should better host these images you want somewhere else and then, get their reference via props.
import React from 'react';
import image1 from '../../../public/assets/image1.jpg'
import image2 from '../../../public/assets/image2.jpg'
import image3 from '../../../public/assets/image3.jpg'
class LandingPage extends React.Component {
componentDidMount() {
setTimeout(()=>{
this.props.history.push('/pagenotfound');
},30000)
}
render() {
return (
<div className="landingPageImg">
<img src={this.props.backgroundImage} className="landingPageImg"/>
</div>
);
}
}
export default LandingPage;
// usage:
<LandingPage backgrounImage={image1} />

SCSS compilation in an isomorphic React app

I'm writing an isomorphic React app based on :
https://github.com/choonkending/react-webpack-node
Instead of css modules used in the examples I'd like to use scss though. And for some reason I'm having a really hard time getting them to work. My first step was to remove the css webpack loaders from both the server and the client configs replacing them with scss-specific loaders (as well as removing postcss) :
loaders: [
'style-loader',
'css-loader?modules&localIdentName=[name]_[local]_[hash:base64:3]',
'sass-loader?sourceMap',
]
But this throws ReferenceError: window is not defined when building as style-loader is apparently not suitable for server-side rendering. So my next idea was to use isomorphic-style-loader. As far as I understand to get it working I need to decorate my component with their higher order component withStyles:
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from '../assets/scss/common/index.scss';
const App = (props, context) => (
<div className={classNames('app')}>
<h1 className="home_header">Welcome!</h1>
{props.children}
</div>
);
export default withStyles(s)(App);
and then do some trickery in the code rendering page on the server. But the problem is, example from the package docs shows a flux action fired inside Express (https://libraries.io/npm/isomorphic-style-loader#webpack-configuration), and the boilerplate that I'm using uses react-router. So I'm kinda lost as how should I inject this object with insertCss into context. I tried this :
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match, createMemoryHistory } from 'react-router';
import axios from 'axios';
import { Provider } from 'react-redux';
import createRoutes from 'routes.jsx';
import configureStore from 'store/configureStore';
import headconfig from 'components/Meta';
import { fetchComponentDataBeforeRender } from 'api/fetchComponentDataBeforeRender';
const clientConfig = {
host: process.env.HOSTNAME || 'localhost',
port: process.env.PORT || '3001'
};
// configure baseURL for axios requests (for serverside API calls)
axios.defaults.baseURL = `http://${clientConfig.host}:${clientConfig.port}`;
function renderFullPage(renderedContent, initialState, head = {
title: 'cs3',
css: ''
}) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
${head.title}
${head.link}
<style type="text/css">${head.css.join('')}</style>
</head>
<body>
<div id="app">${renderedContent}</div>
<script type="text/javascript">window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};</script>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
</body>
</html>
`;
}
export default function render(req, res) {
const history = createMemoryHistory();
const store = configureStore({
project: {}
}, history);
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
const css = [];
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const context = { insertCss: (styles) => css.push(styles._getCss()) };
const InitialView = (
<Provider context={context} store={store}>
<RouterContext {...renderProps} />
</Provider>
);
fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params)
.then(() => {
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
res.status(200).end(renderFullPage(componentHTML, initialState, {
title: 'foo',
css
}));
})
.catch(() => {
res.end(renderFullPage('', {}));
});
} else {
res.status(404).send('Not Found');
}
});
}
but I'm still getting Warning: Failed context type: Required context 'insertCss' was not specified in 'WithStyles(App)'. Any ideas how to tackle this ? And more importantly - is there no easier way to do it ? This seems like a lot of additional work.
There's a few parts to handling scss compilation with webpack when you're doing server-side rendering. First of all, you don't want node trying to import .scss files into your components.
So set a global variable WEBPACK: true in your webpack config:
plugins: [
new webpack.DefinePlugin({
'process.env': {
WEBPACK: JSON.stringify(true),
}
})
],
And in your components, only attempt to import .scss files if the component is being handled by webpack (either during build or development):
if (process.env.WEBPACK) require('../assets/scss/common/index.scss');
If you only have one Sass file per component (you should) then this is always just a one-liner. Any additional Sass files can be imported inside index.scss if you need to.
Then in your config you probably still want the css loader, so it should look like this for your dev server:
{
test: /\.s?css$/,
loaders: ['style', 'css', 'sass']
},
And something like this for you build config:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract('style', 'css!sass')
},

ReactDOM.unmountComponentAtNode: Uncaught ReferenceError: ReactDOM is not defined

I practice react
I met this error : Uncaught ReferenceError: ReactDOM is not defined
when type ReactDOM.unmountComponentAtNode(document.body) on chrome console
Please help me check the problem
I try the code on JSbin and works well,so I think it's webpack problem,but I have no idea .
And I notice there are many way write React.render part when I google ,what's the difference?? which one is correct??
React.render(<App name='Vipul' />,document.body);
ReactDOM.render(<App name='Vipul' />,document.body);
React.renderComponents(<App name='Vipul' />,document.body);
Here is my code:
main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
console.log('Start')
var App = React.createClass({
render: function(){
console.log('render');
return <h1 onClick={this.toggleState}>Hello</h1>
},
componentWillUnmount: function(){
//在console執行 ReactDOM.unmountComponentAtNode(document.body)
console.log('componentWillUnmount');
},
toggleState: function(){
this.setState({status: !this.state.status})
}
});
ReactDOM.render(<App name='Vipul' />,document.body);
webpack.config.js
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: "./src/main.js",
output: {
filename: "./dist/bundle.js"
// filename: "./public/dist/bundle.js"
},
plugins: [
new WebpackNotifierPlugin()
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ['es2015', 'react']
}
}
]
},devtool: 'source-map'
};
ReactDOM available since 0.14.0, so
ReactDOM.render(<App name='Vipul' />,document.body);
should be fine. If you are using lower version of React then React.render
Secondly it is recommended not to render on document.body, rather create a div inside the body and render there.
I met this error : Uncaught ReferenceError: ReactDOM is not defined
when type ReactDOM.unmountComponentAtNode(document.body) on chrome console
when you use Webpack the React and ReactDOM will not be available globally. So, this code will only work inside the file/module where you have imported React & ReactDOM.
There is only one way to render React component in browser and it's this one:
ReactDOM.render(<App />, target);
All other are deprecated - renderComponent is renamed to render and moved from react package to react-dom package.
Also, do you need to call unmountComponentAtNode in your componentWillUnmount lifecycle method? In your particular case it does not make sense. React will unmount component for you - for free.
componentWillUnmount usually serves for clearing timeouts/intervals and/or canceling async operations (Promises, closing connections, ...);
Removing that call won't harm you, try it out.

Resources