I'm pretty new to React and I'm having some issues with my project. I feel like I'm going insane, the simplest things are not working as I'd expect them to. Having re-read my code what feels like hundreds of times at this point, I still can't figure out for the life of me what the problem is. Specifically, I was testing a simple button with an onClick function (onClick={() => console.log("test")}). The button renders fine, but clicking it doesn't log anything in the console.
I'm also using Webpack and Express (though I doubt Express is causing any issues at this point).
I've tried searching for similar issues but nothing has helped thus far.
Below is my index.jsx:
import React from 'react';
import ReactDOM from 'react-dom';
import styles from './index.module.scss';
function App() {
return (
<>
<button onClick={() => console.log("Working")} >Test</button>
</>
)
}
ReactDOM.render((
<App />
), document.getElementById('root'));
module.hot.accept();
Here is my index.ejs file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="main.bundle.js"></script></head>
<body>
<div id="root">
</div>
<script src="main.bundle.js"></script>
<% for (var css in htmlWebpackPlugin.files.css) { %>
<link href=".<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet" />
<% } %>
</body>
</html>
Here's the webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = (env, args) => {
return {
entry: './src/index.jsx',
...(options()),
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.scss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
],
},
resolve: {
extensions: ['*', '.js', '.jsx'],
},
plugins: plugins(),
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
}
};
};
const plugins = () => {
const resOut = path.resolve(__dirname, 'dist');
const plugs = [
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: [resOut],
dry: false,
}),
new HtmlWebpackPlugin({
hash: true,
filename: path.resolve(resOut, 'index.html'),
template: './src/index.ejs',
})
]
return plugs;
}
const options = () => {
const ops = {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
compress: true,
port: 9000,
historyApiFallback: true,
openPage: '',
hot: true,
proxy: {
'/api/**' : {
target: 'http://localhost:3000',
secure: false,
}
},
allowedHosts: [
'localhost',
]
},
};
return ops;
}
I'm also using this small batch script to run the webpack dev server and start Express:
#echo off
cd %~dp0
start cmd.exe /k npm run serve
start node ./express/app.js
npm run build -- --watch
I'm not sure if this is related, but the React dev tools show that 'App' is rendered 3 times. I'm not sure if that is some quirk with the dev tools or if it may be causing some issues. Again, I've tried to find if anyone has experienced anything similar, but couldn't find anything.
I'm happy to share any other information that might be needed to get to the bottom of this. Thanks in advance for any help you can give.
You're importing index.bundle.js twice , try uncommenting the script in index.ejs since webpack automatically imports it , this should fix your issue
Related
I have created module A which is a component library for my React App. Which I plan on using on module B which is my actual React App.
I have an index.js whereby I export my components from module A by using loadable components in the following fashion
import loadable from '#loadable/component'
export const Theme = loadable(() => import('./Theme'))
export const OtherComponent = loadable(() => import('./OtherComponent'))
export const OtherComponent2 = loadable(() => import('./OtherComponent2'))
I therefore build and deploy module A to npm by using the following webpack configuration
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin")
const LoadablePlugin = require('#loadable/webpack-plugin')
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
minimize: true,
concatenateModules: false,
minimizer: [new TerserPlugin({
terserOptions: {
keep_fnames: true
}
})],
},
entry: {
main: './src/components/index.js',
},
output: {
publicPath: '/',
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
library: "myComponentLibrary",
libraryTarget: "umd",
globalObject: "this"
},
externals: {
react: {
root: 'React',
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
},
},
plugins: [new CleanWebpackPlugin(), new LoadablePlugin()],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: true
}
}
],
},
{
test: /\.(jpe?g|png|gif|svg)$/,
type: 'asset/inline'
},
]
}
}
I expected that when I npm install module A on module B to be able to import and render my components but instead I get the following error.
loadable-components: failed to asynchronously load component { fileName: undefined, chunkName: undefined, error: 'Loading chunk 2661 failed.\n(error: 2661.js)' }
Please provide some guidance on how I can solve this issue
If everything is working well on development but for production is not and you face with this error, Add this <base href="/"/> to head of index.html:
<!DOCTYPE html>
<html>
<head>
<base href="/"/>
<meta charset="utf-8" />
<title>Foo project</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Now, I think everything is working well.
This problem is because of html5 routing, you can search about it.
so I'm creating a ReactJS app and configuring webpack for it.
below's my webpack config:
webpack.config.js
const webpack = require('webpack');
const HtmlWebPackPlugin = require( 'html-webpack-plugin' );
const path = require( 'path' );
const dotenv = require('dotenv');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const env = dotenv.config().parsed;
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
module.exports = {
context: __dirname,
entry: ['./src/js/index.js','./src/sass/index.scss'],
output: {
path: path.resolve( __dirname, 'dist' ),
filename: 'main.js',
publicPath: '/',
},
devtool: 'eval-source-map',
devServer: {
historyApiFallback: true,
contentBase: path.join(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: [/node_modules/],
query: {
presets: ['#babel/react', "#babel/preset-env"]
}
},
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: [/node_modules/],
query: {
presets: ['#babel/react', "#babel/preset-env"]
}
},
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(gif|png|jpe?g|svg|webp)$/i,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true, // webpack#1.x
disable: true, // webpack#2.x and newer
},
},
],
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: path.resolve( __dirname, 'public/index.html' ),
filename: 'index.html',
}),
new webpack.ProvidePlugin({
"React": "react",
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'public/assets' }
]
}),
new webpack.DefinePlugin(envKeys),
]
};
It was all going well until I realized that all my buttons aren't working, I checked all the functions and the syntax to make sure if I get it wrong, but turns out I am making no mistake about it
here's the function to my button:
const submitMail = e => {
console.log('here')
alert('here')
e.preventDefault()
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation();
console.log('test')
}
I tried to call it in my test button, but it wouldn't work, it sent no alert, it won't even log all my console.logs
<button onClick={e => submitMail(e)}>test</button>
I also tried to put them in my form to trigger it at onSubmit along with another button inside just to see if it triggers the "console.log" which is also not working:
<form onSubmit={e => submitMail(e)}>
<input id="banner-text-input-email-input-wrapper-email-input" placeholder="Enter your email here"/>
<input id="banner-text-input-email-input-wrapper-submit-input" onClick={()=>console.log('hi')} type="button" value="Button Value"/>
</form>
NOTE:
I've tried to run the normal react app:
react-script start
and all the buttons are working!! at this point, I can only think that the reason it's not working is because of my webpack config, here's also my webpack command just in case:
webpack serve --open --mode=development --config=webpack.config.js
--hot
Instead of
<button onClick={e => submitMail(e)}>test</button>
did you try to use
<button onClick={submitMail}>test</button>
It will implicitly send the event if I'm not mistaken...
okay, it turns out, I'm very silly at this thing, I'm very new to webpacks, so I thought I needed to put
<script type="text/javascript" src="main.js"></script>
in my public/index.html file
after I deleted that part, it all went well.
I'm trying to create JS widgets from my existing react app. So currently I have an app that looks something like this
-src
- config
- components
- containers
- lib
- assets
- widgets
- widgetOne
- widgetTwo
- components
- widget.js
- index.js
- index.js
- index.html
So, I want directories in the widgets directories to be self contained apps that I can break out into a separate js file and a client can just add the js script into their page in a script tag.
I've come close but still facing a few issues. Also, I wanted to see if someone had experience doing this following a better pattern.
Right now I'm using webpack to do this splitting. I'm just defining /src/widgets/widgetOne/index.js as an entry point and perfectly creates a separate file.
Here is my webpack:
const appConstants = function() {
switch (process.env.NODE_ENV) {
case 'local':
const localConfig = require('./config/local');
return localConfig.config();
case 'development':
const devConfig = require('./config/development');
return devConfig.config();
case 'production':
default:
const prodConfig = require('./config/production');
return prodConfig.config();
}
};
const HtmlWebPackPlugin = require("html-webpack-plugin");
const webpack = require('webpack');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const htmlWebpackPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html",
hash: true
});
let webpackConfig = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
exclude: [ /assets/, /node_modules/ ],
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]_[local]_[hash:base64]",
sourceMap: true,
minimize: true
}
}
]
},
{
test: /\.(pdf|jpg|png|gif|svg|ico)$/,
exclude: [/node_modules/],
use: [
{
loader: 'file-loader'
},
]
},
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
exclude: [/node_modules/],
use: {
loader: 'url-loader?limit100000'
}
}
]
},
entry: {
main: [ "#babel/polyfill", "./src/index.js"],
widgetOne: ["./src/widgets/widgetOne/index.js"]
},
output: {
publicPath: appConstants().BASENAME ? JSON.parse(appConstants().BASENAME) : '/'
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
plugins: [
htmlWebpackPlugin,
new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /en/),
new BundleAnalyzerPlugin({
analyzerMode: 'disabled',
generateStatsFile: true,
statsOptions: { source: false }
}),
new webpack.DefinePlugin({
'process.env': appConstants()
}),
new webpack.EnvironmentPlugin(['NODE_ENV'])
],
devServer: {
historyApiFallback: true,
port: 9090
}
};
module.exports = webpackConfig;
The problem I have now is while I get the widgetOne.js:
1) I end up with a vendor~widgetOne.js file that I also need to include to make the widgetOne app to work.
2) The widgetOne.js also gets added to my index.html file for my main app which I do not want.
Is there a way to configure webpack properly to make this work?
So, this is the solution I came up with that seems to work. I still don't know if this is the best approach but it' the only one I was able to get to work for me.
I decided to build the widgets as a different environment process and and modify the webpack configs based on that environment.
So the package.json I add this line under scritps:
"build-widgets": "cross-env NODE_ENV=plugins webpack --mode development",
And I added this section to the end of my webpack.config.js file:
// Override webpack configs when building plugins
if ( process.env.NODE_ENV === 'plugins') {
webpackConfig.entry = {
widgetOne: [ "#babel/polyfill", "./src/plugins/widgetOne/index.js"]
}
webpackConfig.output = {
publicPath: appConstants().DS_BASENAME ? JSON.parse(appConstants().DS_BASENAME) : '/',
path: __dirname + '/dist/widgets',
library: 'MyApp',
libraryTarget: 'umd',
umdNamedDefine: true
}
}
Alternatively, I could have just added a second webpack.config.js exclusively to deal with my widgets build. In my case I didn't feel the need for it just yet but it's something to be considered for sure, just for the sake of keeping configs separate.
I got my webpack Hot Module Replacement working. I was told that once it is working, I would no longer have to do a full refresh for my code. This is not the case! I still require a refresh when I make changes to my code! (App.js).
How can I properly enable webpack HMR?
Link to the project on github
This is my entry point
import './styles/index.css';
import App from './components/App';
import React from 'react';
import { render } from 'react-dom';
const rootDOMNode = document.getElementById('app');
function renderRoot() {
render(<App/>, rootDOMNode);
}
renderRoot();
if (module.hot) {
module.hot.accept('./components/App', () => {
console.log('Accepting the updated module');
renderRoot();
});
}
webpack.config.js:
const webpack = require("webpack");
const path = require("path");
const CleanWebpackPlugin = require( "clean-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: [
"./app/index"
],
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "dist"), // Note: Physical files are only output by the production build task `npm run build`.
publicPath: "/",
filename: "bundle.js"
},
devServer: {
contentBase: path.resolve(__dirname, "dist"),
hot: true,
port: 3000,
open: true,
compress: true
},
plugins: [
new ExtractTextPlugin({
disable: false,
filename: "css/style.css",
allChunks: true
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
module: {
rules: [
{ test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
} },
// { test: /(\.css)$/, use: ["style-loader", "css-loader"] },
{ test: /(\.css)$/, use: ExtractTextPlugin.extract(["css-loader"]) },
{ test: /\.(png|svg|jpg|gif)$/, use: ["file-loader"] },
// for fonts
{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: ["file-loader"] }
]
}
};
The reason it is not working is because you have to re-require your app once you get the hot update, otherwise you are just re-rendering your original app.
the following code should work:
import './styles/index.css';
//import App from './components/App';
import React from 'react';
import { render } from 'react-dom';
const rootDOMNode = document.getElementById('app');
let App;
function renderRoot() {
App = require('./components/App').default; // we have to re-require this every time it changes otherwise we are rendering the same old app.
render(<App/>, rootDOMNode);
}
renderRoot();
if (module.hot) {
module.hot.accept('./components/App', () => {
console.log('Accepting the updated module');
renderRoot();
});
}
I use hot reloading with webpack-dev-server by adding a script in package.json.
webpack-dev-server --output-public-path=/dist/ --inline --hot
I am trying to set up webpack hot reloading with react-hot-loader. It mostly seems to be working. I am using webpack in an existing rails app.
But it isn't hot-reloading. It is simply triggering a reload every time my react code is changed. The error messages I get are:
[HMR] Cannot apply update. Need to do a full reload! - dev-server.js:18
[HMR] Error: Aborted because 0 is not accepted - dev-server.js:19
at hotApply (http://localhost:8080/assets/webpack/bundle.js?body=1:380:31)
at hotUpdateDownloaded (http://localhost:8080/assets/webpack/bundle.js?body=1:293:13)
at hotAddUpdateChunk (http://localhost:8080/assets/webpack/bundle.js?body=1:273:13)
at webpackHotUpdateCallback (http://localhost:8080/assets/webpack/bundle.js?body=1:5:12)
at http://localhost:8080/assets/webpack0.bd89931b2fa8e2901794.hot-update.js:1:1
Navigated to http://lvh.me:3000/react_page
Here is my webpack.hot.config.js settings:
var path = require('path');
var webpack = require('webpack');
var config = module.exports = {
// Set 'context' for Rails Asset Pipeline
context: __dirname,
entry: {
App: [
'webpack-dev-server/client?http://localhost:8080/', // WebpackDevServer host and port
'webpack/hot/only-dev-server', // "only" prevents reload on syntax errors
'./app/frontend/javascripts/app.js' // Your appʼs entry point
],
vendor: ['jquery', 'react', 'react-dom', 'react-redux', 'redux','redux-thunk']
},
devtool: 'inline-source-map',
// Require the webpack and react-hot-loader plugins
plugins: [
//new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.CommonsChunkPlugin(
{
name: 'vendor',
chunks: [''],
filename: 'vendor.js',
minChunks: Infinity
}),
new webpack.NoErrorsPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jquery': 'jquery'
})
],
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loaders: [
'react-hot',
'babel?presets[]=es2015&presets[]=react'
],
cacheDirectory: true
}
]
},
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts', 'webpack'), // Save to Rails Asset Pipeline
filename: 'bundle.js', // Will output App_wp_bundle.js
publicPath: 'http://localhost:8080/assets/webpack',
//publicPath: 'http://localhost:8080/assets/webpack' // Required for webpack-dev-server
},
resolve: {
extensions: ['', '.js', '.jsx'],
modulesDirectories: ['node_modules'],
},
};
And I run the code with
webpack-dev-server -d --config webpack.hot.config.js --hot --inline
The rails development environment serves the webpack files outside the application asset pipeline via the webpack-dev-server because of the following setting in my development.rb file.
config.action_controller.asset_host = Proc.new { |source|
if source =~ /webpack\/bundle\.js$/i
"http://localhost:8080"
end
}
I have been trying to get this working for hours. Any help would be appreciated.
Thanks guys!
Ok i was getting the same error, but after trying some things out i figured this out: My root component was a stateless functional component (pure function). I refactored it to a class component and BAM! the hot reloading is working again.
Before:
const App = (props) => (
<div>
<Header links={headerLinks} logoSrc={logoSrc} />
{props.children}
</div>
);
After:
class App extends React.Component {
render() {
return (
<div>
<Header links={headerLinks} logoSrc={logoSrc} />
{this.props.children}
</div>
);
}
}
As the answers provided above are still not working on my side with webpack 5,
Here is the working config from webpack
In webpack.config.js
devServer: {
.
.
.
hot: true,
}
In the webpack entrypoint index.js add
if (module.hot) {
module.hot.accept();
}
In package.json start script
"scripts": {
.
.
"start": "webpack serve --config /webpack.config.js"
},
I recently ran into this exact issue, the fix for me was removing this from the entries array: 'webpack-dev-server/client?http://localhost:9000/',.
As I was also running --hot as a command line argument, there were two instances of webpack-dev-server getting into a bad state.
I don't know if this will specifically help your issue, but I encountered this error recently as well - i fixed it by adding a .js extension to the module I was trying to set up with hmr - here was my code
if (module.hot) {
module.hot.accept('app/Routes', () => (
getRoutes = require('app/Routes')
))
}
I updated it to getRoutes = require('app/Routes.js') and the error disappeared, using webpack ^2.0.0-beta.
it also works if i add the JS extension as the first argument of hot accept like so:
if (module.hot) {
module.hot.accept('app/Routes.js', () => (
getRoutes = require('app/Routes')
))
}
so now it matches whats on the webpack HMR page
I ran into a similar problem.
After 2 days of research and trying different things, I found out the simplest cause to my problem ever:
in webpack.config.js, I had a HRM dev server enabled. And I also had a HMR server run from command line. Thanks to hint from Tyler Kelley (see above), just by removing --hot from command line, it works OK now.
current webpack.config.js
devtool: "inline-source-map",
devServer: {
publicPath: '/dist/',
contentBase: path.join(__dirname, 'public'),
port: 9000,
hot: true
},
With this configuration, don't do this:
npx webpack-dev-server --hot --inline
Do this:
npx webpack-dev-server
For "webpack": "^5.74.0" and "webpack-dev-server": "^4.11.1".
I set devServer.static = ['./webpack-dev-server', './dist'], in webpack.config.js.
There is my config:
module.exports = {
devtool: 'inline-source-map',
devServer: {
open: true,
static: ['./webpack-dev-server', './dist']
},
entry: path.join(__dirname, 'src', 'index.ts'),
module: {
rules: [
{
test: /\.(ttf|png|svg|jpg|jpeg)$/i,
type: 'asset/resource',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.join(__dirname, 'tsconfig.json')
}
}
]
},
mode: 'development',
output: {
filename: '[name][contenthash].js',
path: path.join(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'),
inject: 'body'
})
],
resolve: {
extensions: ['.js', '.ts']
}
}