React: Lerna React hooks error on monorepo - reactjs

I have a problem with using Lerna.
My folders structure are like shown below:
packages
myapp
shared
myapp represents a create-react-app structure and shared contains some functions that is returning as below:
import React from 'react'
import Card from '#material-ui/core/Card';
function Hello(){
return (
<Card>
This is a test
</Card>
)
}
When I use material ui components instead of DIV's i get error that says:
Invalid hook call. Hooks can only be called inside of the body of a function component.
But when I use div and other html elements it works fine.
Maybe I got this error because of babel loader? I don't know what to do and how to setup things.

This happens because you have multiple versions of React installed, and more than one get's bundled into the app.
Try to add this to your webpack config:
config.resolve.alias = {
'react': path.join(__dirname, '..', '..', '..', 'node_modules', 'react'), // Adjust for your path to root node_modules
};

Related

How can I use absolute paths in my React styleguidist component library?

I have a React component library that is used in a React app. The component library is setup using Styleguidist and webpack. I've setup webpack to use absolute paths using:
webpackConfig: {
resolve: {
modules: [path.resolve(__dirname, 'src/'), 'node_modules'],
}
}
This works within the context of the component library. When I build the component library, the package looks like this:
/core
/components
/Table
/Row
When I import the components into my app, I get an error:
Module not found: Can't resolve components/Row in /Users/myusername/Sites/mysite/node_modules/#mypackage/core/components/Table
I understand why the paths don't match in the context of node_modules, but I would've expected Webpack to transform those import paths during the build process. Is there something I'm missing? Or is this not possible?
While Styleguidist uses webpack, it turns out the build script we were using does not, so the webpack config is irrelevant. Instead, our build script (https://www.npmjs.com/package/cod-scripts) uses babel.
We ended up having to add a separate babel.config.js file to define absolute paths for babel using the babel-plugin-module-resolver package.
npm install babel-plugin-module-resolver --saveDev
npm install #babel/preset-react --saveDev
babel.config.js
module.exports = {
plugins: [
[
'module-resolver',
{
root: ['./src'],
},
],
],
presets: ['#babel/preset-react'],
};

How to import ReactJS Material UI using a CDN through Webpack's externals?

The Problem:
I'm trying to create a website (web app) with React and Material UI, it's working just fine using npm. But when I try to make them as externals and import them through a CDN instead, I get an error with Material UI (React works fine).
My Code:
I linked CDNs in index.html like this:
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/#material-ui/core/umd/material-ui.production.min.js"></script>
<script src="app.min.js"></script>
And in app.min.js, I imported them like this:
import { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button } from '#material-ui/core';
My Attempt:
In the webpack.config.js, I tried the following (again, only Material UI causes an error):
Using a string:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'#material-ui/core': 'Button'
}
gives:
Uncaught ReferenceError: Button is not defined
Using an object:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'#material-ui/core': {
Button: '#material-ui/core'
}
}
gives:
TypeError: Cannot read property 'Button' of undefined
Doing it manually, so Material UI isn't in externals:
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
Then removing minified Material UI code from app.min.js, this leaves the code incomplete and it doesn't run.
Searched through GitHub issue and SO questions without any luck, some links:
How should material-ui be externalized when bundling with webpack
Externals defined in webpack.config still getting error module not found
React CDN: Webpack externals library not resolved in code
Any idea how can I solve this?
Solution::
in webpack.config.js:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'material-ui': 'window["material-ui"]'
},
then in app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Button } from 'material-ui';
Explanation:
If you check the cdn version of material ui js, you will find it exports its content in to material-ui namespace.
if you config webpack like:
'material-ui': 'material-ui'
webpack will compile it to:
which leads to the code looking for material and ui in global environment which does not exist. So we have to specify window["material-ui"] explicitly
Might be a little late to the party but I will add an answer which worked for me.
step 1:
add the script tag from unpkg. The difference between this and cdnjs is that unkpg have an option for umd. May or may not be an issue in your particular situation. It is for me.
url:
https://unpkg.com/#material-ui/core#4.11.0/umd/material-ui.production.min.js
script tag:
<script src="https://unpkg.com/#material-ui/core#4.11.0/umd/material-ui.production.min.js"></script>
step 1b:
add the font and font icon external resources as described in the material-ui docs:
material-ui getting started - installation guide
roboto font:
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
font icons:
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
step 2:
destructure the elements you want to use from window.MaterialUI or use the square bracket notation (but unnecessary here since this package ditched the '-' char.
const { Button } = window['MaterialUI'];
step 3:
use the element as you 'normally' would
<Button variant="contained" color="primary">
Primary
</Button>
I have just solved this issue after spending way too much time on this issue while trying to build an app using a micro-frontend architecture.
TL;DR;
The solution is to put the following in the webpack.config.js:
module.exports = {
// rest of the config clipped for brevity
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'react-router-dom': 'ReactRouterDOM',
'#material-ui/core': 'MaterialUI'
}
};
Further details:
I was building a macro-frontend composed of multiple micro-frontends. Each of the micro-frontends was developed in React, exported as a web-component, and would be independently deployed such that each micro-frontend would be available via a URL like:
http://foo.example.com/main.js
http://bar.example.com/main.js
http://baz.example.com/main.js
These would be imported into the macro-app using a <script> tag.
The macro-app was hosted on a separate domain, e.g., http://example.com.
The issue I was facing was that Material UI (and possibly React as well) was being initialized multiple times in each of the micro-apps.
To avoid that, I had to externalize all these libraries using the webpack config block above.
I had to make 2 concessions.
I did not use create-react-app and react-scripts to scaffold the macro-app because that setup would hide the webpack config. In order to expose the webpack config, I could either eject the CRA project, or use some other modules, such as react-app-rewired, etc. That felt like too much work. The downside of this was that I could not use BrowserRouter and had to accept using HashRouter for client-side routing.
I could not use SvgIcon-based icons from #material-ui/icons, because I could not find a good way of externalizing Material UI Icons. Instead, I put in a link to Material UI Icons stylesheet, and opted to use Icon from #material-ui/core/Icon to render icons. using SvgIcon-based icons was causing Material UI to be initialized in the micro-apps too, which is what I was trying to avoid. One upside of the workaround is that Icon works with Font Awesome as well, so at least all icons would be written consistently in code.
Overall, I am happy with the end results.

preact compat causing component to mount infinitely

I have working reactjs app with redux and react router.
I want to switch to preact.
When I updated webpack config to alias react and react dom.
Component started mounting infinitely. It mount then unmount .. goes on doing so.
Webpack config.
alias: {
"react": path.resolve(__dirname, 'react.js'),
"react-dom": path.resolve(__dirname, 'react.js')
},
react.js
var preact = require('preact-compat');
var react = {};
Object.keys(preact).forEach(function(key) {
react[key] = preact[key];
});
module.exports = react;
This file is because I am using react hot loader.
React app is working fine .. but adding preact has bug. pls guide me.
From my experience, preact-compat and React Hot Loader (RHL) don't work together very well (it's still on the TODO list of the developer of preact-compact, see this comment).
I work around these issues myself by not combining the two, i.e. use the "real" React and ReactDOM when I want to use RHL, or use preact-compat when I don't need RHL.

using webpack ProvidePlugin in React storybook custom webpack config

I've built a wrapper package for drag and drop in React, and I added storybook examples.
Since in my consumer React is exposed globally, i'm not importing React explicitly.
In the storybook examples I need to supply React as part of the custom webpack config, but for some reason it can't resolve React and I get a ReferenceError: React is not defined
This is the package - https://github.com/fiverr/drag_n_drop_package
And this is the custom webpack config file:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ProvidePlugin({
React: 'react'
})
],
module: {
loaders: [
{
test: /\.scss$/,
loader: 'style!raw!sass'
}
]
}
};
This is really strange but your storybook webpack.config.js is mixing webpack v1/v2.
Importing webpack as
const webpack = require('#kadira/storybook/node_modules/webpack');
solves it because it uses the same webpack reference that storybook is using (v1).
I found the following code in webpack.config.js at github:
externals: {
react: 'React'
}
It looks as this question. If it needs to load the React lib from external, like CDN. The page has to be sure have a script tag for importing React lib. And make sure this script tag is in front of the bundle.js or the file which it generated by webpack, so the Object of React will exist when the following code needs to use React, such as:
<script src="./react.js"></script>
<script src="./bundle.js"></script>

How to import from different bundle created with webpack

I have two project using webpack. Now I want to bring one project as module of other project. I can get the two bundle created but don't know how to import from the other bundle.
Elaborating a bit:-
Lets say the other file from which i want to import looks like as follows:-
index2.js (Bundled as bundleTwo)
import SomeCompoent from "./components/SomeCompoent/SomeCompoent";
module.exports = {SomeCompoent}
and in the file (is in another bundle - bundleOne) below I want to import the component (somecomponent):-
index1.js (in bundleOne)
import {SomeCompoent} from "bundleTwo";
but here bundleTwo is undefiend
Any help is highly appreciated
One way that I have figured out myself, is that using alias this can be achieved.
To make this line import {SomeCompoent} from "bundleTwo"; work, bundleTwo can be defined in alias :-
config:{
resolve: {
alias: {
"bundleTwo": path.join(__dirname, "<path_to_the_bundleTwo>")
}
....
If you want to use webpack only,then just set the libraryTarget to 'umd' in bundletwo webpack configuration.
In order to be able to import this module, you need to export your bundle.
output: {
libraryTarget: 'umd',// make the bundle export
filename: "index.js",
path: path.resolve(__dirname, "dist"),
}
However, this can also be achieved by just using Babel to transpile your ES6 code to ES5 code.
babel index2.js --out-file dist/index2.js
Now set the main in package.json to "dist/index2.js"
Now you can use it like
import {SomeCompoent} from "bundleTwo";
You can also create a gulp script for that
gulp.task('js', function () {
return gulp.src(['packages/**/*.js', "!**/*.test.js"])
.pipe(babel({
plugins: ['transform-runtime']
}))
.pipe(gulp.dest('dist'));
});

Resources