React Single Page app with browserHistory possible? - reactjs

I've been building a react single page app using hashHistory from react-router, and things have been working fine until I decided to remove the trailing code in the URL that looks like this: #/?_k=ncbx6v.
The recommended solution I came across was to transition to browserHistory, but I noticed all the examples and solutions require using webpack-dev-server with history-api-fallback set to true. I tried this approach and it worked (going through localhost), but the standalone bundle.js file + index.html that once worked no longer works.
When I run webpack and open the html file, I get this error in the console:
Warning: [react-router] Location "/Users/mike/project/index.html" did not match any routes
I am not familiar with the mechanics behind the issue, but I'm curious if there's a solution out there I'm not familiar with.
This is my webpack file:
const {resolve} = require('path')
module.exports = () => {
return {
context: resolve('src'),
entry: './app',
output: {
path: resolve('public'),
filename: 'bundle.js',
publicPath: '/public/'
},
resolve: {
extensions: ['.js', '.jsx', '.json']
},
stats: {
colors: true,
reasons: true,
chunks: false
},
module: {
rules: [
{enforce: 'pre', test: /\.jsx?$/, loader: 'eslint-loader', exclude: [/node_modules/]},
{test: /\.jsx?$/,loader: 'babel-loader', include: /src/, exclude: /node_modules/
},
{test: /\.json$/, loader: 'json-loader'},
{test: /(\.css)$/, loaders: ['style-loader', 'css-loader']},
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader"},
{test: /\.(woff|woff2)$/, loader: "url-loader?prefix=font/&limit=5000"},
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=application/octet-stream"},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url-loader?limit=10000&mimetype=image/svg+xml"}
]
}
}
}
And this is my app.js:
'use strict'
import React from 'react'
import ReactDOM from 'react-dom'
import Layout from './components/common/Layout'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import configureStore from './store/configureStore'
import { Provider } from 'react-redux'
const store = configureStore()
class App extends React.Component {
render () {
return (
<Provider store={store}>
<Router history={browserHistory}>
<Route path='/' component={Layout}></Route>
</Router>
</Provider>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))

In order to use the browser history, you will need a backend that can handle routing for all of your possible routes. If you do not plan on having a backend that can support routing (e.g., you will be serving static files) you should stick to the hash history.
The basic explanation of how browser history works is that it looks at the pathname of the current URL and attempts to match that against the known routes. In your included error, your pathname is /Users/mike/project/index.html. That means that in order for React Router to match that URL, you would have to have defined the <Route>'s path (or have a series of nested <Route>s) to be /Users/mike/project/index.html.
<Route path='Users/mike/project/index.html' component={App} />
The hash history works with static files because it just appends a hash symbol after the pathname and determines the route by what falls after that.
If your issue is just that you do not like having the query key (the ?_k=jkadjlkd), you can specify that that should not be included when you create your history instance. The URLs will still include the #, but no longer have the key "junk" attached to them.
import { Router, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false })
<Router history={appHistory} />

Related

React router dom is not working in the main app

Good morning,
I am having an issue when working with React router dom using my own webpack configuracion. I know using create-react-app will solve (almost) any issues and is friendly to use, but we would like to have more flexibility.
This is my main application:
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Login from '../pages/Login'
import { AppContextProvider } from '../context/AppContext'
const MainApp = () => {
return (
<AppContextProvider>
<Router basename={'nynweb'}>
<Switch>
<Route exact path='/' component={Login} />
<Route exact path='/login' component={Login} />
</Switch>
</Router>
</AppContextProvider>
)
}
export default MainApp
As you can see, it´s very simple. If I use localhost:3000/nynweb in the browser, it works perfectly and the router redirects to the login page. Then, If I type localhost:3000/nynweb/login, it should do exactly the same (at list to my understanding), but it doesn´t. I have the Cannot GET /nynweb/login response instead.
Internally the routing is working, though. I mean, when in the components I use the useHistory push method, it redirects properly (history.push('/login')).
What am I missing? Below I am posting my webpack configuration. Maybe the problem lies there:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const BabelRules = {
test: /\.(js)$/,
use: ['babel-loader'],
exclude: /node_modules/,
}
const CSSRules = {
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
}
const ImageRules = {
test: /\.(woff(2)?|ttf|otf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'static/fonts/',
},
},
],
}
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/nynweb/',
clean: true,
},
optimization: {
minimize: true,
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [BabelRules, CSSRules, ImageRules],
},
resolve: {
extensions: ['.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
}
Below I have included a screenshot with the applicacion tree, so that you can see where the static folder is located (all the assets lie there)
As the documentation of Webpack states, the publicPath option allows to serve assets, like images, SVGs etc. What's probably going on is Webpack searching for an asset with the name 'login', but can't find it.
I'd advise you to change this string to your public folder where you store assets (typically, /public).
Also, the example in React-Router documentation adds a forward slash before the basename string.
Clarifications
You set your publicPath variable to /nynweb/, and what Webpack understands is that everything under the '/nynweb/*' path is to be served as an asset.
Change it to, let's say, /nynweb/public/, and now, everything under the '/nynweb/public/*' path is to be served as an asset.
Routes that are not '/nynweb/public/some_route' will now work as expected.

Navigate to a specific page by url in React

How do I navigate to a specific page with url in React?
I realized I've used CRA in the past and haven't really tackled this specifically.
Currently I'm rendering ReactDOM with BrowserRouter and Switch with exact path in Routes
// index.jsx
/* eslint-disable import/extensions */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './app/App.jsx';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
// App.jsx
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import Header from '../components/Header';
import HomePage from '../pages/Home';
import PlansPage from '../pages/Plans';
import NotFoundPage from '../pages/NotFound';
const App = () => (
<BrowserRouter>
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/plans" component={PlansPage} />
<Route component={NotFoundPage} />
</Switch>
</BrowserRouter>
);
export default App;
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
module.exports = {
devtool: false,
module: {
rules: [
{
test: /\.m?(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: [{
loader: 'style-loader',
options: {
sourceMap: true,
},
}, {
loader: 'css-loader',
options: {
sourceMap: true,
},
}, {
loader: 'sass-loader',
options: {
sourceMap: true,
},
}],
},
],
},
entry: './src/index.jsx',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: './index.html',
}),
new webpack.SourceMapDevToolPlugin({}),
],
};
Navigating to http://localhost:8080/plans leads to a page with Cannot GET /plans. However, clicking on a Link element navigates to that plans page without an issue.
Yeah since you're using webpack Dev Server all you have to do is add historyApiFallback=true and it'll fix you issue. You're essentially telling all routes to fall back to your index.html file. Hope that helped.

React-router-v4 default route not working with Express backend

I'm using react-router-v4, React 16, and Express 4 for a web application. I use a BrowserRouter component for my main application to render all my routes. All of the component routes and the default route (404) render fine when requesting a single level subdirectory (example.com/asdf), but when one or more subdirectories are being requested (example.com/asdf/zzz), the default route fails to match the request and render, and I'm simply left with a blank page. Here is an example of my app.js with my routes:
import React, { Component } from 'react';
import {
BrowserRouter, Switch, Route
} from 'react-router-dom'
class App extends Component {
constructor() {
super();
}
render() {
return (
<BrowserRouter>
<Switch>
<ScrollToTopRoute exact path="/" component={Cover}/>
<ScrollToTopRoute path="/faq" component={FAQ}/>
<ScrollToTopRoute path="/signup" component={Signup}/>
<ScrollToTopRoute path="/login" component={Login}/>
<Route component={FourOhFour} />
</Switch>
</BrowserRouter>
);
}
}
export default App;
Like I said, I'm using Express for hosting this web application. These are the relevant routes for that.
// Serve up index as React app under build dir
app.use('/', express.static(path.join(__dirname, 'build')));
// Handles all other react routes so no 404 errors
app.get('/*', function (request, response){
response.sendFile(path.join(__dirname, 'build/index.html'));
});
If I console.log inside of the app.get route, on nested subdirectories like example.com/asdf/zzz, it seems to loop 4-5 times before finally ending on a blank screen. I've tried removing all other routes besides the default route in app.js, and the results are the same, it works fine until nested subdirectories.
EDIT: Someone asked for my webpack config, so here it is:
// webpack.config.js
const HtmlPlugin = require('html-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
module.exports = {
// Tell webpack to start bundling our app at src/index.js
entry: [
'./src/index.js'
],
// Output our app to the dist/ directory
output: {
filename: 'app.js',
path: path.resolve(__dirname, "build")
},
// Emit source maps so we can debug our code in the browser
devtool: 'source-map',
// Tell webpack to run our source code through Babel
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.css$/,
loader: [ 'style-loader', 'css-loader' ]
},
{
test: /\.(png|jpg|gif|svg|ico)$/,
loader: 'file-loader?name=./public/img/[hash].[ext]'
},
{
test: /\.(json)$/,
loader: 'file-loader?name=./public/assets/[hash].[ext]'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
loader: 'file-loader?name=./public/fonts/[hash].[ext]'
},
{
test: /\.(html)$/,
options: {
attrs: ['link:href']
},
loader: 'html-loader'
}]
},
// Since Webpack only understands JavaScript, we need to
// add a plugin to tell it how to handle html files.
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.EnvironmentPlugin(['NODE_ENV'])
]
}
EDIT: Some more information. It seems like the Express routes are working, and rendering all non-existant routes using response.sendFile, but the default route appears to stop working when using nested subdirectories.

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
})
],

Uncaught Error: Element type is invalid

I used React few month ago, now when I tried to use that code, made some changes in webpack config I'm getting errors.
I changed react-router to react-router-dom.
Whole routes file is here
https://github.com/mstanielewicz/react-simple-boilerplate/blob/master/app/config/routes.js
I'm getting:
Uncaught Error: Element type is invalid: expected a string (for
built-in components) or a class/function (for composite components)
but got: undefined. You likely forgot to export your component from
the file it's defined in.
I went through the code a few time and don't see any errors. Components and Containers are defined and exported correctly.
import React from 'react'
import {
BrowserRouter as Router,
Route,
hashHistory,
IndexRoute,
} from 'react-router-dom'
import { MainContainer, HomeContainer } from 'containers'
export default function getRoutes() {
return (
<Router history={hashHistory}>
<Router path='/' component={MainContainer} >
<IndexRoute component={HomeContainer} />
</Router>
</Router>
)
}
Directory structure looks like this
- app
|_components
| |_Home
| |_index.js
|_containers
|_Main
|_Home
|_index.js
with exports like this
export MainContainer from './Main/MainContainer'
export HomeContainer from './Home/HomeContainer'
And webpack cfg
var path = require('path')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
template: path.join(__dirname, 'app/index.html'),
filename: 'index.html',
inject: 'body',
})
module.exports = {
devtool: 'cheap-module-inline-source-map',
entry: [
'./app/index.js',
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'index_bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
},
],
},
resolve: {
modules: [
path.join(__dirname, 'app'),
'node_modules',
],
},
plugins: [HtmlWebpackPluginConfig],
}
Anyone can see a problem here?
Seems like a problem of import to me.
In react-router-dom, Router as been replaced by BrowserRouter. Or may be a bad default export, for example
import BrowserRouter from 'react-router-dom
instead of
import { BrowserRouter } from 'react-router-dom'
EDIT :
Your exports seems a bit weird.
Try to change import { MainContainer, HomeContainer } from 'containers' into
import MainContainer from './containers/Main/MainContainer'
import HomeContainer from 'containers/Main/HomeContainer'
and export default class FooContainer in the respective files.
There is also the fact you're using a Router inside a Router. Shouldn't it be a Route ?

Resources