React Router v6 - Handle both external hrefs and interal paths - reactjs

In my react app with react-router v6 I am retrieving links dynamically that can be either a regular external url (like https://stackoverflow.com/) or an internal path (like "/my-page") that corresponds to a Route with react-router.
So far, when using react-router-dom's <Link> component, I could only get it to work with an internal path, but NOT with an external url. As far as I understood the docs I couldn't find a way to achieve this, at least not with v6.
So the best approach I've come up with so far is to write an own MyLink component that either render an <a> or a <Link> depending on whether the href is external or not:
function MyLink(props) {
const isHrefExternal = props.href.match(/^http|^https|^www/);
if (isHrefExternal) {
return <a href={props.href}>{props.children}</a>;
}
return <Link to={props.href}>{props.children}</Link>;
}
This of course doesn't look like a very good solution:
The check for isHrefExternal is very naive
It's unclear what props MyLink should accept and how they should be managed since <a> and <Link> have different props.
For a full example see this codesandbox
Can you tell me how to do it better? Ideally there would be an option to pass an external url to react-router-dom's <Link> component but I couldn't find one.
Thanks a lot!

The Link component handles only internal links within your app, and likely won't ever handle external links natively.
I don't see any overt issues with your code, and I don't think it's a bad solution. The "isExternal" check may be naive, but it only needs to cover your specific use cases. As new use cases are required you can tweak the logic for this check. In my projects I've worked with we've just typically included an isExternal type property with fetched data so the app doesn't even need to think about what is or isn't an external link, it just checks the flag when rendering.

Related

How to target specific components through Links in react-router?

I want to insert links throughout the content of a React app, where links trigger 'navigation' only within specific Components in the tree. Ideally I would use a react library to do this, rather than invent my own framework from scratch.
Existing approaches that I can find, such as react-router, seem to assume that every routed component should only be visible when a route path matches it, rather than routes being able to selectively send 'control' signals to matching components, while unmatching components should not be affected at all.
My intended application needs independent navigation within different panes, similar to the behaviour of a HTML Frameset ( see e.g. this JavaDoc single-page navigation - https://docs.oracle.com/javase/7/docs/api/ ) where specific links have a href (a route in React) but also a 'target' which indicates which pane needs to be affected by a given navigation.
I am aware I could write a bespoke React eventing pattern. For example I could pass hooks to make changes through tree state, with my own bespoke hash or history eventing in place to monitor clicks. Before I consider writing my own framework for this, I want to understand what other approaches there are and I think I must be overlooking something obvious.
I have put together a repository which simplifies the problem from a react-router point of view.
https://github.com/cefn/graphql-gist/tree/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames
This app has 'Frames' with embedded links. However, every Frame's component in the React tree has to be switched out for another matching component (or none) when a Link is clicked. Ideally I should be able to add a 'target' attribute or otherwise specialise a Link or target so that only a targeted part of the tree is affected by a matching link.
It should be possible for example, to cause the color of the name='left' or name='right' frames to change independently in https://github.com/cefn/graphql-gist/blob/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames/src/FrameSet.js
I am hoping for something from the mainstream react ecosystem which supports routing (e.g. well-documented components with hash listening, history support) but not where every Link affects every Route in the page.
Here is a solution which exploits react-router and isn't totally horrible.
function FrameSet(props) {
return <Router>
<FilterPath pathPrefix="/left/">
<View />
</FilterPath>
<FilterPath pathPrefix="/right/">
<View />
</FilterPath>
</Router>
}
A FilterPath always passes on its pathPrefix value to its childrens' props, and optionally, (when the react-router location matches the pathPrefix) passes on a pathSuffix too.
In this way, each View above only receives a pathSuffix update when a route path begins with their pathPrefix and hence 'targets' them.
A draft working implementation of FilterPath is...
function FilterPath(props) {
return <Route render={({ location: { pathname } }) => {
const { pathPrefix } = props
let pathSuffix
if (pathname.startsWith(pathPrefix)) {
pathSuffix = pathname.slice(pathPrefix.length)
}
return React.Children.map(props.children, child => React.cloneElement(child, { pathPrefix, pathSuffix }))
}} />
}
A working example using FilterPath can be seen at https://github.com/cefn/graphql-gist/blob/2de588be6c2d30b92d452f71749377c9dc6c19c7/react-router-frames/src/FrameSet.js#L22

How do you pass data from one view to the other via react-router-dom without using url parameters?

I use react-router-dom v 4.3.1 for client-side routing. I'm pretty new to React and can't figure out how to pass data from one view to the other without using url parameters. In Angular, the Angular router has a data property where you can pass data associated with a route. An example would be:
const appRoutes: Routes = [
{
path: 'hero/:id',
component: HeroDetailComponent,
data: { title: 'Hero Detail' }
},
];
Can you do the same in react-router-dom? If not, how would you recommend I pass data in React?
Thanks in advance for the help!
<Route path="hero/:id" render={() => <HeroDetailComponent title= "Hero Detail" />} />
Read this: Pass props to a component rendered by React Router
Or if you are using <Link> you can use pass through location object
<Link to={{ pathname: 'hero/:id', state: { title: 'Hero Detail'} }}>My route</Link>
Well you Could use the context API to create a sort of global AppState that you could update in your first component and use in your second component.
You could also abuse the localStorage API by setting a key with the data in the first component and getting it in the other.
However both of these are workarounds that Shouldn't have to be used. Why do you want to redirect to a page but not pass data to it using URL parameters.
There'a several solutions. React being a library, not a framework, doesn’t force you into a single one.
One way is to use the context api. It’s like a link to an object shared between different components.
Another one is redux, which uses context underneath, and gives you a single store for the whole app. You changes values dispatching actions to the store, so it’s a bit tricky to learn the first time.
Using a stream library would open up a lot of different options, but it’s harder to get into. Check refract if you want to go this way.
A poor man’s stream approach that may serve you is using document as a bus to pass data arround, using addEventListeners to receive data and dispatch new customEvent to send it.
Next is the simplest one of all, share a simple object. Using imports form your components, you can import the same object on both and that will be a single instance where data can be shared. Simple JavaScript. It’s not the react way though, because changes won’t trigger a repaint on the component.

react router - keep the query string on route change

I would like to create routes that support query string.
When i say support i mean, passing it to the next route some how.
For example:
given this route: domain/home?lang=eng
and when moving to route domain/about i want it to keep the Query String and display domain/about?lang=eng.
I was sure there's a built in functionality for this but after reading the docs and a lot of search on the net, i couldn't find an elegant solution.
I'm using react-router#3.0.0 and react-router-redux#4.0.7
For react-router 4.x, try
const { history }
history.push('/about' + history.location.search)
To access this.props.history, make sure you have wrapped the component with withRouter HOC
import { withRouter } from 'react-router-dom'
...
export default withRouter(component)
refer https://github.com/ReactTraining/react-router/issues/2185
You will have to "forward" query param on each page transition - bothering and you can easily forgot to...
Instead, I would do this.
read stored/persisted lang preference. localStorage is good candidate here. Fallback to default language, when no preference is found
share lang via context, so that each and every component can read this value.
create some button (or whatever), which would modify this value
Since you are using redux, I would pull redux-persist to persist this preference across page reloads.

Possible to limit list of params when using React Router?

Using React with React-Router in a project and am wondering if it's possible to limit the possibilities for param names, like so:
www.mydomain.com/books/:id
and allowing 'Catcher in the Rye' and 'To Kill A Mockingbird' to be passed through, like so:
www.mydomain.com/books/catcher-in-the-rye
www.mydomain.com/books/to-kill-a-mocking-bird
I want to say that only a specific set of books can be used in place of :id (just so someone can't type in www.mydomain.com/books/whatever-they-want and have an empty React component render).
I do currently have a '*' route that catches anything not mentioned, but because params are dynamically generated based on whatever is passed, that won't help in this case.
Is this possible? Thanks.
You need to handle this logic in the component. Depending on if this is an already mounted component or not you will need to put the logic in the appropriate function (componentDidMount, componentWillReceiveProps)
if(!(this.props.params.id in myAcceptableParameters)){
redirect to a 404 here
}

Using react-router-redux can Link components be used to transition after loading a routes state

I have been using react-router-redux which allows for my location to be stored in state nicely, and allows me to use react-router for defining routes, and linking between routes.
One problem seems to be that when I need to navigate using a <Link /> component like so:
<Link to={`/movie/${m.id}`}>
<p>{m.name}</p>
</Link>
this will trigger the change of route, and on load of the new route, in this case the <Movie /> route, the state for the movie page is loaded.
This doesn't really work nicely for transition between pages, as the new page will be blank on navigation, or you can put in a loader but really it should not navigate to the new page until the api call for the new page has loaded. Similar to sites like pitchfork.com or theguardian.com
This implementation has been discussed in this question and the approach of just using a loader between page transitions is used in the real-world-example and reddit api example
My question is, if I want to load state for the new container/route in my site before navigating, do I need to follow the approach suggested by dax chen in this question?
To sum up the answer was to fire an action and using redux-thunk we can preload the state by calling the action for the new container/route, then on completion we navigate to the page using react-router-redux's push() action
store.dispatch(push('/foo'))
If this is the recommended approach for preloading a page before transition, then I will be changing my react-router-redux-example and my current site to use this approach.
An action to load the new route's data then navigate will look something like this:
function fetchInit(url, id, to) {
return dispatch => {
return dispatch(fetchMovie(url, id))
.then(function(home) {
return dispatch(push(to))
})
}
}
It will need some logic to identify the new container's action to call and also the new path to navigate to. It may call the static fetchData() method on the new page's container component.
So this will somewhat complicate the <Link /> component, I am just looking for some advice on if this is the correct way, or I may have missed a simpler more recommended approach.
<Link onClick={ () => dispatch(navigate(`/movie/${m.id}`, 'Movie')) } >
<p>{m.name}</p>
</Link>
I made an implementation that solves this, and it is possible to make any type of custom link component and have it act in any way you like, however it is probably not a good idea for it to wait for a long load event before changing to a route, as users want the route transition to be immediate. The better approach is to use a nice loader and mix it in to the design of your page well, similar to what is done on pitchfork.com and also to ensure data comes back from the API quickly using caching e.g. redis, memcached.
Here is the library I made showing a custom react-router-redux link component: https://github.com/StevenIseki/react-router-redux-link

Resources