Configuring app's basename in react-router - 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>

Related

Read json angular depends on build or serve

small query
i need to read JSON file when serve mode and Read JSON file from dist folder after build in angular
can you suggest a way how i can achieve
This is why we have environment variables. You should, in your src folder have an environments folder (if not... create), one ts file for prod and one for dev (ng serve). By default the prod should look like:
export const environment = {
production: true
};
and the dev:
export const environment = {
production: false
};
So what you simply do... wherever you need to... import the environment file where you need it... For example:
// insert correct path
import { environment } from '../environments/environment';
and just do an if check or use ternary operator... So something like this:
if (environment.prod) {
// production mode, act accordingly
} else {
// development mode, act accordinly
}

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.

How do I create a "fat" js file with rollup using esm?

I have the following code..
// ui.js (generated by rollup
import Vue from 'vue';
import VueRouter from 'vue-router';
(()=>{
console.log("Wow it actually works");
Vue.use(VueRouter);
const routes = [
{
path: '/',
component: Viewport
}
];
const router = new VueRouter({
mode: "history",
routes: routes
});
window.app = new Vue({ router });
window.app.$mount('#jg-app');
})();
<script src="ui.js" type="module"> </script>
The problem is when I run this I get...
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
This leads me to believe I need a "fat" js that includes dependencies.
I also want to keep everything in es6 modules and avoid introducing say babel.
Is there a way to do this using rollup?
Update
Tried this...
import Vue from "./vue";
But then I get...
Error: Could not resolve './vue' from src/index.js
As far as I can tell this is not possible. I instead had to move the import from the ui project to the server project and create a static js file that looked like this...
//client
import Vue from "./vue"
let app = new Vue(...);
app.$mount('#jg-app');
and import the esm.browser version
// server
app.use('/vue', express.static(__dirname + '/node_modules/vue/dist/vue.esm.browser.js'));
// template
script(src="/main.js" type="module")
Now Vue is working, however, dependencies like Vue-Router appear to not have this es.browser style file.
This is not a solution, it's a workaround
The below rollup config is not esm, it's just a way to create a bundle with dependencies included.
You get one minified browser-compatible JS file.
Here's my working example rollup.config.js (you should replace input: 'src/index.js' with your web app entry point and output.file with a location for the generated bundle):
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import builtins from 'rollup-plugin-node-builtins';
import babel from 'rollup-plugin-babel';
import visualizer from 'rollup-plugin-visualizer';
import { terser } from "rollup-plugin-terser";
const browserPlugins = [
resolve({browser: true}), // so Rollup can properly resolve cuid
babel({
exclude: 'node_modules/**',
babelrc: false,
presets: ['es2015-rollup'],
}),
// builtins(),
commonjs(),
visualizer(),
terser(),
]
export default [
// browser-friendly UMD build
{
// external: Object.keys(globals),
input: 'src/index.js',
output: {
name: 'thinflux',
file: './dist/browser/thinflux.min.js',
format: 'umd'
},
plugins: browserPlugins,
}
];
One more thing: express should statically serve the output.file path, not your source files

React Router with a base path

I would like to add "/app" as my base path for all routes in react routes. So I am trying -
.... more routes
I am unable to make webpack dev server serve pages with URL localhost:8080/app. It gives me a "Cannot get /app" error. If I try localhost:8080/ - it gives me an error that it cannot match a route with "/".
What should be a basic webpack dev server configuration for this scenario?
The Webpack historyApiFallback config option is what you're looking for. Just set that to true and all requests that don't route to an asset will be rewritten to /. You can also pass in an object with custom rewrites:
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' }
]
}
(Example taken from the documentation page linked above.)

In React Router, how do you pass route parameters when using browser history push?

Using the newer version of React Router, how do you pass parameters to the route you are transitioning to if you are transitioning using the browserHistory.push()? I am open to using some other way to transitioning if browserHistory does not allow this. I see that in my top most child component in React Router there is this.props.routeParams but I can't seem to get this populated with values I want from the previous route when transitioning.
Now you will have do something like this
history.push({
pathname: '/about',
search: '?the=search',
state: { some: 'state' }
})
Here is the link for API documentation
for react router v4 we can do things this way:
const history = useHistory();
history.push({
pathname: "/create-opinion/"+user,
state:{ username:userName}
};
and to get it from the other component simply:
const history = useHistory();
then to get username:
const username = history.location.state.username;
don't forget to import useHistory from react-router-dom
if you haven't install react-router-dom
yet type:
$ npm install --save react-router-dom
If you want to pass a path parameter you can do it as follows. I am using sample component Merchant.
Define Route
<Route exact path="/merchant/:id" render={(props) => <Merchant id={props.match.params.id}/>} />
Define History
const history = createBrowserHistory({
basename:''
})
Add to history with parameter
history.push(`/merchant/${id}`)