React Router not behaving as I would like it to - reactjs

I have a webpack, react, flux, react router setup. I have these two routes:
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={Photos} onEnter={someAuthCheck}>
<Route path="/login" component={Login}></Route>
</Route>
</Router>
),document.getElementById('app'));
When I write http://localhost:8080/login in the browser I get:
Cannot GET /login
rather than my login dialog
I am running on the webpack dev server. What am I not doing right?
My webpack config:
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var BUILD_DIR = path.resolve(__dirname, 'src/client/public');
var APP_DIR = path.resolve(__dirname, 'src/client/app');
var config = {
resolve: {
alias: {
jquery: "jquery/src/jquery"
}
},
entry: {
main: APP_DIR + '/index.jsx',
},
output: {
publicPath: "/src/client/public/",
path: BUILD_DIR,
filename: '[name].js'
},
module : {
loaders : [
{ test : /\.jsx?/, include : APP_DIR, loader : 'babel-loader' },
{ test: /.(woff|woff2|eot|ttf)$/, loader:"url-loader?prefix=font/&limit=5000" },
{test: /\.(scss|css)$/, loader: ExtractTextPlugin.extract('css-loader!sass-loader')}
]
},
plugins: [
new ExtractTextPlugin("[name].css"),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
}),
new HtmlWebpackPlugin({
title: 'PhotoTank',
template: 'src/client/app/html-template.ejs',
filename: '../index.html'
})
],
devServer: {
//publicPath: "/src/client/public/",
//historyApiFallBack: true,
// progress: true,
//hot: true,
//inline: true,
// https: true,
//port: 8081,
contentBase: path.resolve(__dirname, 'src/client'),
proxy: {
"/api": {
target: "http://localhost:5000",
pathRewrite: {"^/api" : ""}
}
}
},
};
module.exports = config;

Usually, if you encounter Cannot GET error, it is an issue with webpack-dev-server. From the docs:
To prepare, make sure you have a index.html file that points to your bundle. Assuming that output.filename is bundle.js:
<script src="/bundle.js"></script>
So, you will have to use webpack to generate an index.html file first, usually called npm run build if you are using some boilerplate, or you have to create a separate webpack config for production build to do so.
Alternatively, just create an empty index.html file as instructed above.
There are also some problems with your react-router configuration:
<Route path="/login" component={Login}></Route>
should be
<Route path="login" component={Login}></Route>
There shouldn't be preceding slashes in children routes.
Also, you should be using browserHistory instead of hashHistory if you want to access the page at /login.

You are using hashHistory, so instead of
http://localhost:8080/login
open this:
http://localhost:8080/#/login it will work.
From Doc:
Hash history uses the hash (#) portion of the URL, creating routes
that look like example.com/#/some/path.
Read the difference between hashHistory and browserHistory.

Related

React Route does not work with nested URLs

I am trying to use a switch that routes different components depending on a URL. This URl should be able to be nested, like courses/:cid/assignments/:aid.
This is how my Switch looks like:
<Switch>
<Route path='/courses/:cid/assignments/:aid' component={Assignment} />
<Route exact path="/" component={Home} />
</Switch>
The app runs fine: I can access the home page. However, when I access for example courses/1/assignments/20, I get the following error:
Loading failed for the <script> with source “http://localhost:8081/courses/1/assignmentsets/bundle.js”.
I fiddled around a bit with the paths, just to see where it went wrong and these are the results:
<Switch>
<Route path='/courses/:cid/assignments/:aid' component={Assignments} /> // Does not work
<Route path="/courses/randomnestedurl" component={Assignments} /> // Does not work
<Route path="/courses" component={Assignments} /> // Works fine
<Route path="/:aid" component={Assignments} /> // Works fine
<Route exact path="/" component={Home} /> // Works fine
</Switch>
Does anyone have an idea what could be wrong? Something with my webpack? Some setting I'm missing?
If more code needs to be provided, please let me know which parts of my application would be required.
EDIT: This is my webpack.config.js:
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const APP_PATH = path.resolve(__dirname, 'src');
module.exports = {
entry: APP_PATH,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '/dist'),
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json']
},
module: {
rules: [
{ test: /\.(ts|js)x?$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test:/\.(s*)css$/, use:['style-loader','css-loader', 'sass-loader'] },
],
},
devServer: {
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
secure: false
}
}
},
plugins: [
new HtmlWebpackPlugin({ inject: true, template: path.join(APP_PATH, 'index.html') }),
new ForkTsCheckerWebpackPlugin(),
]`enter code here`
};
That the browser is trying to load your bundle.js from http://localhost:8081/courses/1/assignmentsets/bundle.js indicates that the bundle.js is written as bundle.js instead of /bundle.js in the src attribute of the script tag in your index.html file.
To fix this you could add a publicPath to your Webpack config.
module.exports = {
entry: APP_PATH,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '/dist'),
publicPath: '/'
},
// ...
}
Looks like you have your script in the site like:
<script src="bundle.js"></script>
Edit it to be (/ before filename):
<script src="/bundle.js"></script>

Not able to access URL manually in ReactJS build

I have created build using Webpack 4.2.0. But I'm not able to access URLs directly without landing to index page.
My WebPack Config:
var webpack = require('webpack');
const HtmlPlugin = require('html-webpack-plugin')
// require('react-select/dist/react-select.css')
var path = require('path');
var BUILD_DIR = path.resolve(__dirname, 'build');
var APP_DIR = path.resolve(__dirname, 'src/');
var CSS_DIR = path.resolve(__dirname,'public/css');
// const HtmlPlugin = require('html-webpack-plugin');
var config = {
entry: APP_DIR + '/index.js',
output: {
path: BUILD_DIR,
filename: 'bundle.js'
},
plugins: [
// Configure HtmlPlugin to use our own index.html file
// as a template.
// Check out https://github.com/jantimon/html-webpack-plugin
// for the full list of options.
new HtmlPlugin({
template: 'public/index.html'
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
// new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.LoaderOptionsPlugin({
minimize: true
})
],
module : {
rules: [
{
test: /\.js$/,
enforce: "pre",
include : APP_DIR,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include : CSS_DIR
}
]
}
};
module.exports = config;
Index.js
render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'));
APP.JS
const Main = () => (
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path='/contact' component={Contact}/>
<Route path='/listing' component={Listing}/>
<Route path='/product/:id' component={Product}/>
</Switch>
</main>
)
In Build folder, two files created.
- index.html
- bundle.js
And I have used http-server to open React Build
./node_modules/.bin/http-server build -p 3007
But In browser, it will throw 404 if I access URL localhost:3007/contact directly.
What is right way to deploy this for production which can be accessed via url say www.reactapp.com/contact
You need to configure your server to serve your index.html for all GET requests. ReactRouter just changes the navigation history, you don't actually leave the page when the route changes. Currently, your server is just serving the index.html page for the / route. Configure it to serve the page on all routes and you will be able to visit the routes by access their urls directly.

Cannot GET "/About" with react-router v4 (Production Help)

I've been reading over all the docs for react-router-dom (v4), and tons of Stack Overflow questions with my same error, but A) They leave a lot of unanswered holes and B) They seem to all be suggesting a development only fix, so I'm hoping to see what people are actually doing in PRODUCTION for this simple scenario and C) I'm probably doing something stupid and the answers aren't working for me, with the error "Cannot GET /about" rendering with no errors in the console.
I'm using Express, React, Node and using Webpack for compiling. I can successfully reach my homepage, and clicking any links takes me to the appropriate components, but manually typing in the URL breaks this, as discussed here and the reasons for this error discussed here.
Most answers suggest adding devServer.historyApiFallback = true and output.publicPath = '/' in the webpack.config.js file, which implies I also need to run npm install --save-dev webpack-dev-server and run it using node_modules/.bin/webpack-dev-server as suggested in the docs. Doing all of this, nothing happens. In fact, it's worse now because I also can't access my home route of '/'.
So before dropping my current config here, 1) What can I do to fix this? 2) Does it even matter? The webpack-dev-server is obviously for development only so what about production?
My webpack.config.js file:
var webpack = require('webpack');
var path = require('path');
var envFile = require('node-env-file');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
try {
envFile(path.join(__dirname, 'config/' + process.env.NODE_ENV + '.env'))
} catch (e) {
}
module.exports = {
devServer: {
historyApiFallback: true,
},
entry: [
'script-loader!foundation-sites/dist/js/foundation.min.js',
'./app/app.jsx'
],
plugins: [
new webpack.DefinePlugin({
'process.env': {
//you don't get to see this
}
})
],
output: {
path: __dirname,
filename: './public/bundle.js',
publicPath: '/'
},
resolve: {
modules: [
__dirname,
'node_modules',
'./app/components',
'./app/api'
],
alias: {
app: 'app',
applicationStyles: 'app/styles/app.scss',
actions: 'app/actions/actions.jsx',
configureStore: 'app/store/configureStore.jsx',
reducers: 'app/reducers/reducers.jsx'
),
},
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
loader: 'babel-loader',
query: {
presets: ['react', 'es2015', 'stage-0']
},
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/
},
{
loader: 'url-loader?limit=100000',
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/
},
{
loader: 'sass-loader',
test: /\.scss$/,
options: {
includePaths: [
path.resolve(__dirname, './node_modules/foundation-sites/scss')
]
}
}
]
},
devtool: process.env.NODE_ENV === 'production' ? false : 'source-map'
};
My app.jsx:
var React = require('react');
var ReactDOM = require('react-dom');
import {Provider} from 'react-redux';
import {BrowserRouter as Router, Route, Switch, Link, HashRouter} from 'react-router-dom';
import Home from 'Home';
import Watch from 'Watch';
import About from 'About';
import AddShow from 'AddShow';
var store = require('configureStore').configure();
import firebase from 'app/firebase/';
// Load Foundation
$(document).foundation();
// App css
require('style-loader!css-loader!sass-loader!applicationStyles');
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/watch" component={Watch}/>
<Route path="/about" component={About}/>
<Route path="/addshow" component={AddShow}/>
</div>
</Router>
</Provider>,
document.getElementById('app')
);
You have to set up your web server (the one that serves index.html with the react app) to redirect all requests to the url of your index.html so that react-router can do its job. That's what the suggested change to webpack.config.js is doing for webpack-dev-server
In your webpack.config.js you need to enable the html plugin so webpack knows where your index.html is:
plugins: [
new webpack.DefinePlugin({
'process.env': {
//you don't get to see this
}
}),
new HtmlWebpackPlugin({
template: 'public/index.html' //or wherever your index.html is
})
],

Routing issue in React with Webpack

I routed in my app as the following:
render(
<Provider store={store}>
<Router history={browserHistory }>
<Route path={"/" component={TopContainer}>
<IndexRoute component={Login} />
<Route path='main' component={Maintainer} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
)
Then I deploy my production bundle.js and index.html directly under my web root (apache in ubuntu).
I can access my app at http://...com/
The problem is that after the main page is loaded with route
<Route path='main' component={Maintainer} />
the content in browser location becomes: http://...com/main
At this time, if I reload the page with the url (http://...com/main) I got a page not found error: "The requested URL /main was not found on this server."
Here the my webpack.production.config.js
var webpack = require('webpack');
var path = require('path');
var loaders = require('./webpack.loaders');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
loaders.push({
test: /\.scss$/,
loader: ExtractTextPlugin.extract({fallback: 'style-loader', use : 'css-loader?sourceMap&localIdentName=[local]___[hash:base64:5]!sass-loader?outputStyle=expanded'}),
exclude: ['node_modules']
});
module.exports = {
entry: [
'./src/index.js'
],
output: {
publicPath: './',
path: path.join(__dirname, 'public'),
filename: '[chunkhash].js'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
loaders
},
plugins: [
new WebpackCleanupPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
drop_console: true,
drop_debugger: true
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new ExtractTextPlugin({
filename: 'style.css',
allChunks: true
}),
new HtmlWebpackPlugin({
template: './index.html',
files: {
css: ['style.css'],
js: ['bundle.js'],
}
})
]
};
But if I run it on the local server, there is not such a problem:
"use strict";
var webpack = require('webpack');
var path = require('path');
var loaders = require('./webpack.loaders');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var DashboardPlugin = require('webpack-dashboard/plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
const HOST = process.env.HOST || "127.0.0.1";
const PORT = process.env.PORT || "3000";
loaders.push({
test: /\.scss$/,
loaders: ['style-loader', 'css-loader?importLoaders=1', 'sass-loader'],
exclude: ['node_modules']
});
module.exports = {
entry: [
'react-hot-loader/patch',
'./src/index.js', // your app's entry point
],
devtool: process.env.WEBPACK_DEVTOOL || 'eval-source-map',
output: {
publicPath: '/',
path: path.join(__dirname, 'public'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.js', '.jsx']
},
module: {
loaders
},
devServer: {
contentBase: "./public",
// do not print bundle build stats
noInfo: true,
// enable HMR
hot: true,
// embed the webpack-dev-server runtime into the bundle
inline: true,
// serve index.html in place of 404 responses to allow HTML5 history
historyApiFallback: true,
port: PORT,
host: HOST
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.HotModuleReplacementPlugin(),
new ExtractTextPlugin({
filename: 'style.css',
allChunks: true
}),
new DashboardPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
files: {
css: ['style.css'],
js: [ "bundle.js"],
}
}),
]
};
I also tried it on a node.js web server and got the same problem.
Is the contentBase: "./public" made it?
thanks
coolshare
With Vincent's suggestion, I added some redirects to my Apache config
DocumentRoot /var/www
Redirect permanent /main http://...com/
This work partially: Apache did route http://...com/main to http://...com/
The problem is that from then on React takes over but it routes it to the login screen instead of what I expected - stay in the current screen http://...com/main when reload the browser page.
"If you route this URL to your index.html then the JS router will activate and display the correct page." is not true: I tried
Redirect permanent /main http://...com/index.html
and Apache did not recognize the http://...com/index.html
But Apache
It seems no way to route it to the main page...

React router + webpack, sub routes not working

I am trying to set up my routers for my app, and have the basic / entry point working (seemingly). It seems when I try to start adding sub routes, it is breaking. I have a pretty straight forward set up right now. I am using react + redux and my render looks like :
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory} >
<Route path="/" component={comp1.Component}>
<Route path="test" component={comp2.Component} />
</Route>
</Router>
</Provider>,
// eslint-disable-next-line no-undef
document.getElementById('app')
);
I am running webpack dev server on localhost:8080, and it serves the first route with no problem, however when I go to localhost:8080/test, I am getting a Cannot GET /test .
Here is my webpack config:
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: 'eval',
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./client/app.jsx'
],
output: {
path: path.join(__dirname, ''),
filename: 'bundle.js',
publicPath: '/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
],
module: {
loaders: [{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: "babel-loader",
include: __dirname,
query: {
presets: [ 'es2015', 'react', 'react-hmre' ]
}
}]
}
}
Unsure what I am doing wrong here, would be grateful for any help. Thanks!
React Router uses the HTML5 history API. This means that 404 responses need to serve /index.html.
The docs mention how this works. You need to add this to your module.exports object:
devServer: {
historyApiFallback: true
}
Note that this only works for the CLI, when using the Node.js API you need to add this as a second parameter:
var server = new WebpackDevServer(compiler, { historyApiFallback: true });

Resources