Nested Routes and browserHistory - react-router

I'm having some trouble working with browserHistory and nested routes. The main issue is entering a path directly or refreshing the browser: works perfectly for non-nested routes but not for nested routes.
I'm using webpack-dev-server locally and I am using the history-api-fallback flag:
Without the history-api-fallback flag: any route I type in directly or refresh on gives me "Cannot GET /..." rendered in the browser window. This is expected behavior.
With the history-api-fallback flag: non-nested routes work fine typed in directly or refreshed on but nested routes do not. I don't get the same react-router(?) error rendered in the browser window but I do get 404s in the console, it looks like the browser is trying to load the webpack bundle from one level up in the path (e.g. if I'm on /schools/edit and I hit refresh, the browser tries to load the webpack bundle.js from /schools/ and 404s out).
I have a very simple setup with only a couple of nested routes. All components are simply rendering a div with text for now (with the exception of App and Schools which both also output this.props.children to render their child routes).
My routes:
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="schools" component={Schools}>
<IndexRoute component={SchoolManager} />
<Route path="manage" component={SchoolManager} />
<Route path="edit" component={SchoolEditor} />
</Route>
<Route path="*" component={Home} />
</Route>
</Router>
Webpack config:
entry: [
'webpack/hot/only-dev-server',
'./app/scripts/main.js',
'./app/styles/main.scss'
],
devtool: 'source-map',
output: {
path: './build/',
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loaders: ['react-hot', 'babel-loader?presets[]=es2015,presets[]=stage-0,presets[]=react'],
exclude: /node_modules/
},
{
test: /\.(html|png)$/,
loader: 'file?name=[path][name].[ext]&context=./app'
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
}
]
},
plugins: [
new webpack.NoErrorsPlugin()
],
watchOptions: {
poll: true
}
NPM script to start webpack dev server (run from inside a Vagrant vm):
webpack-dev-server --host 0.0.0.0 --progress --colors --inline --hot --history-api-fallback
To summarize:
<Link>ing to any of the paths above works fine, /schools, /schools/manage, /schools/edit, etc.
Typing in or refreshing on /schools works fine.
Typing in any of the other nested paths (/schools/manage, /schools/edit) or refreshing on them does not work.
I realized as I was typing this out that 'schools' is also technically a nested route but seems to work fine, so it's likely there's something else going on. Thanks in advance for any insight.

After resigning myself to hashHistory for the longest time, finally came across the solution for nested routes. Just had to change the bundle script src from bundle.js to /bundle.js. The leading / ensures that the browser will load bundle.js from the root directory. Credit to Fastas: Unexpected token < error in react router component

Related

"SyntaxError: unexpected token: ':'" when loading JSON file for ngx-translate with Karma runner

I am trying to setup system tests for an Angular app.
It uses the TranslateModule (ngx-translate) like this:
TranslateModule.forRoot({
defaultLanguage: 'de',
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
missingTranslationHandler: {
provide: MissingTranslationHandler,
useClass: MyMissingTranslationHandler,
},
}),
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
}
I used a proxy in karma.conf.js to adapt the request and the file is now found by the TranslateLoader.
Still, when I run the tests with Karma I get an error on the Karma server:
An error was thrown in afterAll
SyntaxError: unexpected token: ':'
http://localhost:9876/base/src/assets/i18n/de.json?e0ac90c584fb64b071dedb9301cd9342777ed8a2:2
The JSON file should be working fine, since it can be viewed in the browser (with clicking on that link) and also it works fine under normal development environments.
There needs to be some sort of preprocessor (or similar) since Karma doesn't recognize the JSON file, I suppose.
Anyone got a solution for this?
For me it helped now to set included: false in karma.conf.js under files/pattern where I loaded the JSON file.

Angular 7 routing ignore path

I have an Angular 7 application, running .Net Core on the back end.
I have the following routes defined:
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: 'about', component: AboutComponent },
{ path: '**', redirectTo: 'home' }
];
In Visual Studio 2019, this is running at https://localhost:44358.
All works as expected.
But now I want to get metadata for a SAML implementation using sustainsys.saml2.aspnetcore2.
To get the metadata file, I try to enter https://localhost:44358/Saml2/ in my browser.
As expected, because the path does not match anything defined, the default route takes over and I am routed to the home page.
I removed the final path, so I no longer had any default routing for unmatched paths, and then it worked fine to get the metadata.
My question is: Is there any way to redirect to 'home' for all unmatched paths except some configured path (or paths), which would just be ignored as if the default route were not present?
Rather add a path to your base-href in index.html (e.g. <base href="/app/"/>) so that the Angular Router won't pick up paths on your root, then you'll be able to keep your wildcard redirect as is and /Saml2/ won't be intercepted.
Of course, if the app is already in production and you need to preserve URLs, you might not be in a position to make this kind of change.

In react-router v4, is it possible to get the deepest route's param out of that Route component

My use case is to have a universal page view statistic function for react-router v4, so it may looks like:
<Provider store={store}>
<Router>
<Tracker>
<App />
</Tracker>
</Router>
</Provider>
My advanced requirement is to get all params from route, so a URL of /users/kitty/books/199231 can be parsed to:
{
path: '/users/:username/books/:isbn',
params: {
username: 'kitty',
isbn: '199231'
}
}
The problem is, my Tracker component can never get access to a nested route's path and match prop, even if I use withRouter with my Tracker component, it gets a path of /
I know in theory my requirement is not correct because we can put two or more <Router> side by side by side, so my real case would be "get the deepest route's params"
Is it possible to archive this? or is there any solution that my page view statistics can parse a URL to it's corresponding route path and params?

redux-observable perform transition once the action fires

I am using redux-observable with an epic.
return action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }), Observable.of({ type: 'APPLIED_SHOPPING_LISTS' }).delay(5000);
Once the epic finishes firing the 'APPLIED_SHOPPING_LISTS' I want to perform a transition, I am using react-router. What is the best place to do this? I saw redux-history-transitions, is this an add-in I should be using?
Further to add to this, I did use redux-history-transitions and change this to the following
return action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }), Observable.of({ type: 'APPLIED_SHOPPING_LISTS', meta: {
transition: (prevState, nextState, action) => ({
pathname: '/Shopping',
}),
} }).delay(5000);
This does seem to change the url and transition to happen, however the component I have configured under the '/Shopping' path does not render. It just stays on the current page. This is what my route looks like
<Route path='/' component={App}>
<IndexRoute component={LoginContainer} />
<Route path='login' component={LoginContainer} />
<Route path='landing' component={LandingComponent} />
<Route path='Shopping' component={ShoppingPathComponent} />
</Route>
If you're using react-router v3 or before, you can use middleware that will let you push/replace history with redux actions, like react-router-redux. I'm not familiar with redux-history-transitions, but it may (or may not) work similarly.
With react-router-redux, it would just mean emitting the action you want to transition the history, when you want it to happen.
So you'd import the push action creator, and just add it as another action after APPLIED_SHOPPING_LISTS:
Observable.of(
{ type: 'APPLIED_SHOPPING_LISTS' },
push('/Shopping')
)
Altogether, something like this:
import { push } from 'react-router-redux';
const somethingEpic = action$ =>
action$.ofType('APPLY_SHOPPING_LISTS')
.flatMap(() => Observable.concat(
Observable.of({ type: 'APPLYING_SHOPPING_LISTS' }),
Observable.of(
{ type: 'APPLIED_SHOPPING_LISTS' },
push('/Shopping')
)
.delay(5000)
));
If you're using v4, as of this writing react-router-redux is not yet compatible with it, but it's in active development here.

Configuring app's basename in react-router

I'm struggling a bit with react-router 2.x configuration, specifically app basename.
I've an application which may have different base root throughout its lifecycle. For instance:
/ in development
/users in production
/account in production after migration
The basename comes into play in several places:
static asset compilation in Webpack
react-router main configuration
specifying redirect routes in redux actions
providing something like redirectUrl to API calls
My current solution is to have an ENV variable and make it available both to Webpack and to the app itself by injecting window.defs via an Express server, but I still end up having things like ${defs.APP_BASENAME}/signin in way too many places throughout the app.
How can I abstract the app base, or at least tuck it away in a single location? I should be able to specify the base route in Router's config, and then simply use relative routes somehow, right? Or am I missing something?
You can decorate your history with a basename. You could mix this with a DefinePlugin in your Webpack configuration to specify which basename should be used.
// webpack.config.js
new Webpack.DefinePlugin({
BASENAME: '/users'
})
// somewhere in your application
import { useRouterHistory } from 'react-router'
import { createHistory } from 'history'
const history = useRouterHistory(createHistory)({
basename: BASENAME
})
Given the basename: /users, React Router will ignore the /users at the beginning of the pathname so:
The URL /users is internally matched by the path /
The URL /users/profile matches the path /profile.
Similarly, you do not have to append the basename to the path when you are navigating within your application.
<Link to='/friends'>Friends</Link> will navigate to /friends internally, but the URL in the location bar will be /users/friends.
Today I ran into the same issue:
On my localhost I let an NGINX serve the stuff in the root context, but on my prod server, an Apache serves the stuff from a subdirectory...
Inspired by the answer from Paul S and inspired by the infos here:
https://github.com/ReactTraining/react-router/issues/353
I got the for me working solution:
In the Webpack config file I defined a plugin for my localhost dev env:
plugins: [
new webpack.DefinePlugin({
BASENAME: JSON.stringify("/")
})
],
In the Webpack PROD config file I defined a plugin for my prod env in a subfolder, i.e. www.example.com/users:
plugins: [
new webpack.DefinePlugin({
BASENAME: JSON.stringify("/users/")
}),
And in my react-router definitions I just reference:
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { useBasename } from 'history'
...
<Router history={useBasename(() => browserHistory)({ basename: BASENAME })}>
For me a very elegant solution and easy too. It just cost me around five hours of looking around :-)
In React Router V6.
Edit package.json and add homepage : Directory name key value as follows
"homepage" : "https://blog.sangw.in/react-student-management",
OR
"homepage" : "/react-student-management",
and on Routers BrowserRouter add basename : Directory name as follows
<BrowserRouter basename={'/react-student-management'}>
and you are done.
Visit https://blog.sangw.in/react-student-management and app will be deployed and working.
Try this it will work
import { createBrowserHistory } from 'history';
const history = createBrowserHistory({
basename: 'base-name'
})
<Router history={history}>
</Router>