Programmatically navigate using react router without duplicating paths - reactjs

I've read this post, but I don't like having browserHistory.push('/some/path') in one of my components while I have <Route path="/some/path" component={SomePage} /> in my router file since the path is duplicated. What if I want to change that path to /another/path? Now I need to remember to update it in the router file and also my component.
Is there a better way around this? I was thinking that I could have "/some/path" and all my other paths defined in some constants file that gets imported and referenced in my router and my component. Example:
paths.js
var Paths = {
myPath: "/some/path",
...
}
module.exports = Paths
router.jsx
var Paths = require('constants/paths');
...
<Route path={Paths.myPath} component={SomePage} />
component.jsx
var Paths = require('constants/paths');
...
browserhistory.push(Paths.myPath)
This seems like it could get a little messy when dealing with URL parameters like /some/path/:id, so I was hoping there might be a better way.

This is what I have done in the past for routing to make it simpler / more streamlined.
(as a side note i used lodash here so if you aren't you can use native functions to do basically the same thing. lodash just adds a bunch of nice features / functions that you dont need to go write yourself)
in my routes.jsx file you should create functions that convert any parameters into a url with a default path for this answer lets just make one for a profile route
export function pathToProfile(userName, params) {
return path(Paths.PROFILE, _.assign({userName}, params));
}
the path() function is just a simple helper utility function for generating a path.
path(url, params, urlMap) {
if(!url) {
console.error("URL is not defined for action: ", params);
}
if(!params)
return url;
params = _.merge({}, params);
if(urlMap) {
_.keys(urlMap).forEach((k) => {
params[urlMap[k]] = params[k];
delete params[k];
});
}
if(url.indexOf(":") !== -1) {
url.match(/:([0-9_a-z]+)/gi).forEach((match)=>{
var key = match.replace(":", "");
url = url.replace(match, params[key]);
params = _.omit(params, key);
});
}
if(_.keys(params).length > 0) {
url = url + "?" + this.paramsToString(params);
}
return url;
}
now looking at the constants file
Paths {
PROFILE: '/user/:username',
}
Finally usage.
I wouldn't recommend broswerHistory.push() when you can have an onClick handler. Yes it works and will redirect, but is it the best thing to use? react-router also has a Link that should be used wherever possible. you get some nice additional features one for example would be an active route for whatever page you're on.
browserHistory.push() is a good way to handle redirecting when you do things like an auth login redirect or if you are responding to data from a request and conditionally taking them to a page.
<Route path={Paths.PROFILE} component={Profile} />
<Link to={pathToProfile(this.props.user.username, {myParams: 'here'})></Link>
what that would be translated into if you wanted to see the url from that exact link it would be /user/someUsername?myParams=here

Related

How to create dynamic route in gatsby

I have setup gatsby project using this link. It is working correctly.
Now I know how to create route by defining the component inside the pages folder. But now I have a new challenge I need to create one dynamic route so that I can pass my id in it (Just like reactjs).
<Route path: "/path/:id"/>
How do I do that in gatsby?
You have to explicitly tell gatsby that a path should be dynamic. From the docs:
// gatsby-node.js
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
and then you can use dynamic routing in src/pages/app.js
import { Router } from "#reach/router"
const SomeSubPage = props => {
return <div>Hi from SubPage with id: {props.id}</div>
}
const App = () => (
<Layout>
<Link to="/app/1">First item</Link>{" "}
<Link to="/app/2">Second item</Link>{" "}
<Router>
// ...dynamic routes here
<SomeSubPage path="/app/:id" />
</Router>
</Layout>
)
export default App
Everything that goes to /app/* will be handled dynamically now. You should find your id as usual in the props.
Have a look at their authentication example https://github.com/gatsbyjs/gatsby/tree/master/examples/simple-auth
You can use square brackets ([ ]) in the file path to mark any dynamic segments of the URL. For example, in order to edit a user, you might want a route like /user/:id to fetch the data for whatever id is passed into the URL.
src/pages/users/[id].js will generate a route like /users/:id
src/pages/users/[id]/group/[groupId].js will generate a route like /users/:id/group/:groupId
Reference: https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api#creating-client-only-routes
You can use gatsby-plugin-create-client-paths. It uses matchPath. For more info check
https://www.gatsbyjs.org/docs/gatsby-internals-terminology/#matchpath
https://www.gatsbyjs.org/packages/gatsby-plugin-create-client-paths/
This answer is Super late, but for anyone in the future who is faced with this problem, I have a simpler solution.
In Gatsby terms it's called a Splat Route.
For examples, If you want some page "domain.com/profile/[id]", where id can be any number, which will be used to display different data inside the website, you should name your page as [...id].
Now inside the page you can access this id as
const ProfilePage = (props) => <div>This page is for id number {props.params.id}</div>
Note: Don't miss the 3 dots, that is what signifies a splat route in gatsby.

React Router: Handling queries through React router

So, currently, I have a routing component:
<Route path="/lists/:query" component={Lists} />
I get a call like:
http://localhost:4567/lists/page=17&city_codes=2567
In my Lists component, I handle this query in this way:
componentDidMount() {
const query = match.params.query;
const cleanQueryString = query.replace(/[|;$%#"<>()+,]/g, '');
// break up the string using '&' and '=' into an object
const properties = this.queryURL(cleanQueryString);
const cleanQueryObj = _.pick(Object.assign({}, ...properties), [
'page',
'city_codes',
'min_monthly_fee',
'max_monthly_fee',
'order_by',
]);
// update the query object based on component state
this.setState({ query: cleanQueryObj }, () => {
cleanQueryObj.page && this.updateIndex(parseInt(cleanQueryObj.page, 10));
});
// call axios request and update redux
dispatch(handleLists(cleanQueryObj));
// update browser url
this.props.history.push(cleanQueryObj);
Now, I see a lot of major sites using ?q= before the query and I'm wondering what I'm missing or what could be improved?
Thoughts?
While what you are doing is technically valid, it is a bit non-standard. The way you use the router :query param and the way it is formatted, reaaaaly looks like an actual location.search parameter format, and not a path parameter.
A more standard way to do it, would be with the following URL:
http://localhost:4567/lists?page=17&city_codes=2567
And code as follow:
// In your routes, just a simple route with no path params
<Route path="/lists" component={Lists} />
// In your component
import queryString from 'query-string'
[...]
componentDidMount() {
// Use location object from react-router
const { search } = this.props.location
// Parse it using a npm dedicated module
const { page, city_codes } = queryString.parse(search)
// Now you can use those params
]);
Edit: and now an actual answer to the question:
?q=blah is usually used in a search context, with q parameter being a string used to search something. There can be other parameters following for example ?q=blah&ext=txt.
It is hence different from your :query path param, which is encoded to contain multiple parameters, while q here is a single ready-to-use parameter.

Call React component with dynamic URL parameters

For now, I am rendering a React Component with a static URL.
I would like to pass it a dynamic URL.
For instance, rather than calling it like this:
https://www.example.com
I would like to call it like this:
https://www.example.com/?name=John
And I would like this to update its this.props.name or this.state.name components.
Can I do that?
What should I use for this purpose? react-router?
Or can this only be done from the backend?
You do not need react-router. Following would parse your url:
let url_parameter = {};
const currLocation = window.location.href,
parArr = currLocation.split("?")[1].split("&");
for (let i = 0; i < parArr.length; i++) {
const parr = parArr[i].split("=");
url_parameter[parr[0]] = parr[1];
}
However, if you use react-router you might want to go the the resource. Than the link would look like this:
http://www.example.com/john
In this case react allows to get the resource in the this.props.params:
Here how to specify the route:
<Route path="/:name" component={Name}>
Here how to access the name in the component 'Name':
....
render() {
const {name} = this.props.params;
...
}
I hope this helps.

How to get params with backbone router?

I'm actually getting the params like that:
http://localhost:9080/app?{%22address%22:%22rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep%22}
with this route
routes: {
"app":"app"
}
As you can see it's really ugly and I would like to have this simple:
http://localhost:9080/app/rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep
Or something like that
http://localhost:9080/app/#/rsPENV8NaobtaFqHmArDVVhDRRGVjmD5ep
You see the idea...
But when I try app/*address or app/:address it's not working , meaning I can't catch my params or even the page.
So basically /app is server, how to get the param without the ugly way I use ?
Edit: Here is a pretty good example I found in a tutorial:
routes: {
"posts/:id": "getPost",
// Example
"download/*path": "downloadFile",
// Download
":route/:action": "loadView",
// Load Route/Action View
},
So here the question would be how to get something like that
http://example.com/app/#/myparam
The good rule is to separate your routes. In your situation I'd do something like this:
routes: {
"p/:param_name": "getSomething"
};
...
getSomething: function(param_name) {
...
}
But if really don't need to use specific area in the router you should add your target route to the end of your routes.
routes: {
"p/:param_name": "getSomething",
...
":param": "getSomething"
};

NancyFX Catch All route

Does NancyFX supports ASP.NET MVC like 'Catch All' route? I need one, that basically match every URL. This is very handy for building up Single Page applications.
Is that possible?
Tested in Nancy version 0.23.2
Get[#"/(.*)"] did not work for me as a catch-all route. The routes "/", "/foo/bar", and longer routes would not catch. It seems like there's no getting around having to define a Get["/"] route for the root URL. Nothing else seems to catch it (tried Get["{uri*}"]). Here's how I ended up defining my routes (keep in mind I'm doing this for an Angular application):
Get["/views/{uri*}"] = _ => { return "A partial view..."; };
Get["/"] =
Get["/{uri*}"] = _ =>
{
var uri = (string)_.uri;// The captured route
// If you're using OWIN, you can also get a reference to the captured route with:
var environment = this.Context.GetOwinEnvironment();// GetOwinEnvironment is in the 'Nancy.Owin' namespace
var requestPath = (string)environment["owin.RequestPath"];
return View["views/defaultLayout.html"];
};
It's important to understand Pattern Scoring. The route patterns are weighted, if two routes match the same url segment, the higher score wins. The catch-all pattern is weighted 0 and even though the /views/{uri*} route pattern is also a catch-all, it starts with a literal, which is weighted 10000, so it will win out on all routes that start with /views.
Here's more info on Accessing Owin's Environment Variables. Note that the captured uri variable and requestPath will be slightly different. The requestPath will start with a / where as the uri variable will not. Also, if the matched route pattern is Get["/"], uri will be null and requestPath will be "/".
The Views route will return a partial html file, based on the url path, and all other routes will return the default Layout page that will bootstrap the SPA.
Yes, using Regex
Get[#"/(.*)"] = parameters => {
return View["viewname", parameters];
};
But you don't really need it for building a Single Page Application with NancyFX - you can just use Get and Post with all your routing logic and still have a single page app.
An updated answer for whom #synhershko 's solution does not work. Try:
Get[#"^(.*)$"] = parameters => {
return "hi";
};
This will capture all paths except for the index page. I am not sure if this will work in the context of Angular, but this worked for me when trying to hack together a simple server with just one handler.
Answer provided by #synhershko does not work for me. It does not handle /users/2 or any other route containing more segements.
Below code works on my machine ;) :
public class IndexModule : NancyModule
{
dynamic IndexPage() { return View["Index"]; }
public IndexModule()
{
Get["/"] = _ => { return IndexPage(); };
Get["/(.*)"] = _ => { return IndexPage(); };
Get["/(.*)/(.*)"] = _ => { return IndexPage(); };
Get["/(.*)/(.*)/(.*)"] = _ => { return IndexPage(); };
}
}
My solution is not perfect, cause it does not match everything. I repeated as many '/(.*)' as in my longest Angular route.

Resources