Navigating to a 404 Route with Reach Router - javascript

I have the following routing config:
<Router>
<NotFound default />
<ResourcesContainer path="/resources" />
<ResourceContainer path="/resources/:id" />
...
</Router>
This catches any routes that are not handled, and renders the <NotFound /> component at the URL that wasn't found, so if I type example.com/blah, I see the <NotFound /> component rendered, and in the address bar I see example.com/blah. I also use this URL on the page to display a message:
The page 'example/blah' was not found.
So far so good. However I also need to handle 404s from within in /resources/* routes. My <ResourcesContainer/> component uses the last part of the path to hit a GraphQL API for a resource with that id. If the API returns to tell the client that resource doesn't exist, I would like to mimic the behaviour outlined above. However, I don't have a page to navigate to. I could duplicate the <NotFound /> route and give it an explicit path of /404, then navigate to that. However the URL would then be /404 and not the original resources/* path that wasn't found.
The following solves part of the problem, giving me a page to redirect ot, but means the URL is rewritten to /404 in all cases:
<Router>
<ResourcesContainer path="/resources" />
<ResourceContainer path="/resources/:id" />
<NotFound path="/404" />
<Redirect noThrow from="*" to="/404" />
...
</Router>
How can I set this up so that I can navigate to the <NotFound /> route without losing the original URL?

Your best option is to change render method of ResourceContainer to render NotFound if resource is not found.
However, if you don't want to make changes to ResourceContainer, you can wrap it with an error boundary like this:
class NotFoundErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { notFound: false };
}
static getDerivedStateFromError(error) {
// Filter which kind of errors you want to show the error boundary for
return { notFound: true };
}
render() {
if (this.state.notFound) {
// You can render any custom fallback UI
return <NotFound />;
}
return this.props.children;
}
}
And use it like:
<NotFoundErrorBoundary>
<ResourceContainer path="/resources/:id" />
</NotFoundErrorBoundary>
Your ResourceContainer can throw an error NotFoundErrorBoundary can identify and that can signal that resource is not found and it should render NotFound page instead of the children.
To be clear, I am not encouraging you to use ErrorBoundary. In my opinion, it will overcomplicate things. I just present you the information, how you use it is up to you. Also it may be useful to you in another context depending on the use case.

Error boundary isn't a good option to handle it. The official website encourages us use nested route philosophy e.g. The best practice is use a <Router> inside of each child:
Root:
<Router>
<NotFound default />
<ResourcesContainer path="/resources/*" />
...
</Router>
ResourcesContainer.jsx:
<Router>
<NotFound default />
<ResourcesList path="/" />
<ResourceDetail path="/:id" />
...
</Router>
How to render <NotFound /> if we get 404 in API call?
It's easy:
if (API.error) return <NotFound />;
if (API.data) return ...;
How to simplify it?
We can have a wraper for with like this:
RouterWIthNotFound
<Router>
<NotFound default />
{children}
</Router>

This is not a route problem in my opinion. The component that renders /resources/:id basically has three states: loading, foundData and 404. If you just render different components for each state then you can just render the same 404 component. just simple if you don't want to change the url, don't navigate.

Related

Blank page and the console showing me error message about routes [duplicate]

Here is the code screenshot.
I want to render Homepage component but I want to wrap it into these MainLayout component.
The problem is that screen is blank and there is no error in Terminal but when I inspect the page it says "Matched leaf route at location "/" does not have an element", so guys I know this is version update syntax problem because I had same problem when I was writing component= {component } but syntax has been changed and I should have written element={<component />}.
So I believe this is also syntax problem but I've done research but couldn't solve. I believe I should change this
/* ... */ render = {() => (
<MainLayout>
<Homepage />
</MainLayout>
)}
somewhat into newer syntax but don't know how.
The Route components in react-router-dom v6 no longer take component or render props; the routed components are rendered on the element prop as JSX.
<Routes>
...
<Route
path="/"
element={(
<MainLayout>
<Homepage />
</MainLayout>
)}
/>
...
</Routes>

Matched leaf route at location "/" does not have an element react-router [duplicate]

Here is the code screenshot.
I want to render Homepage component but I want to wrap it into these MainLayout component.
The problem is that screen is blank and there is no error in Terminal but when I inspect the page it says "Matched leaf route at location "/" does not have an element", so guys I know this is version update syntax problem because I had same problem when I was writing component= {component } but syntax has been changed and I should have written element={<component />}.
So I believe this is also syntax problem but I've done research but couldn't solve. I believe I should change this
/* ... */ render = {() => (
<MainLayout>
<Homepage />
</MainLayout>
)}
somewhat into newer syntax but don't know how.
The Route components in react-router-dom v6 no longer take component or render props; the routed components are rendered on the element prop as JSX.
<Routes>
...
<Route
path="/"
element={(
<MainLayout>
<Homepage />
</MainLayout>
)}
/>
...
</Routes>

Why would I want to nest my React Routes?

I am having a hard time understanding why I would want to nest my react routes through components. In the following example you have two components spread across multiple files.
File 1:
<Route path='/topics' component={Topics} />
File 2:
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<Link to={`${match.url}/exampleTopicId`}>
Example topic
</Link>
<Route path={`${match.path}/:topicId`} component={Topic}/>
</div>
)
I am currently building a CRM for my company, where instead I am doing this:
routes/index.js
const routes = [
...
{ path: '/app/analytics/competitors', component: Competitors, meta: { title: 'Competitor Data', description: '' } },
{ path: '/app/analytics/competitors/:id', component: CompetitorById, meta: { title: 'Competitor Report', description: '' } }]
export default routes
App.js:
<Provider {...RootStore}>
<Router history={RootStore.routerStore.history}>
<Switch>
{routes.map((route) => {
return (
<Route
key={route.path}
path={route.path}
component={route.component}
exact={true}
meta={route.meta}
/>
);
}
})}
</Switch>
</Router>
</Provider>
When you have dozens of routes, I find this to be much clearer to read, implement and understand what is going on with my router.
It is more similar to the way I have seen routes implemented in other frameworks, such as VueJS's Vue StoreFront.
Is there anything wrong with what I am doing here, and if not, why do people nest their routes?
Thanks :)
Edit:
To further extend, I am using the routes as following for anything that required authorisation:
return (
<AppRoute
key={route.path}
path={route.path}
component={route.component}
exact={true}
meta={route.meta}
/>
);
This named route are being declared like so:
const AppRoute = ({ ...props }) =>
RootStore.userStore.hasSession ? (
<Theme>
<Route {...props} />
</Theme>
) : (
<Redirect to={"/login"} />
);
Within theme we have shared components that are used between every page, such as the navbar and header. This prevents re-rendering, so in essence each route is a container rather than a whole page.
One short but important reason would be re-rendering.
If you've got one route rendering under /foo and one under /foo/:id, then React would not need to re-render the foo route compinent, even when you switch from one id to another. If you don't nest them, everything has to be re-rendered again.
This has even more benefits when you want to add some simple rules, e.g. only allow users to enter /foo when they're authenticated. You can add this rule to your /foo route component. If a user is authenticated, the content is rendered, as well as all sub-routes become available. Without nesting, you would need to implement it in every route again.

react-router-redux redirect to absolute url

I'm migrating a react application and I'm trying to split it. Basically, I would like to redirect some client-side react routes to absolute urls (or relative, but at least go with a server roundtrip, where reverse proxying is done)
Note that
react-router 3.0.0
react-router-redux 4.0.7
The app have these urls
http://myhost/ => homepage
http://myhost/someroute1 => a first route
http://myhost/someroute2 => a second route
http://myhost/someroute3 => a third route
Everything is inside react right now.
Routing looks like this :
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
<Route path="/someroute1" component={Route1} />
<Route path="/someroute2" component={Route2} />
<Route path="/someroute3" component={Route3} />
</Route>
</Router>
</Provider>
The goal is to redirect, say, routes "/" and "/someroute2" to static urls (server urls). As so :
http://myhost/ => http://anotherhost/
http://myhost/someroute1 => keep react routing
http://myhost/someroute2 => http://anotherhost/anotherroute5
http://myhost/someroute3 => keep react routing
The question is simple : is is possible to replace, in a clean way, a react router route with an absolute url ?
I heard about Redirect and IndexRedirect components, but I can't figure how to use it properly, and, due to a lack of react / react-router, I can't figure if there would be any dangerous side-effects (in history for example).
Use Route's render prop instead of component. That way, you can specify a function to be called instead of a component to be instantiated. In the function, perform the navigation the old-fashioned way, using window.location.href:
<Route
path="/someroute2"
render={() => {
window.location.href = "http://anotherhost/anotherroute5";
return null;
}}
/>
Partially based on #brub answer, I've found a solution using a dumb component.
import React, { Component } from 'react'
export default class MyRedirectRoute extends Component {
componentDidMount() {
window.location.href = //my url here
}
render() {
return null
}
}
That I pass like this
<Route path="/someroute3" component={MyRedirectRoute} />
Though, I'm still not aware of a few things :
Is this a recommended solution ?
Are there any history side-effect ?
Is there any better (more react-router) solution than window.location.href ? I tried this.context.history without any success...
I'm waiting for feedback / better solution before accepting my own answer
You probably don't need React Router for this. The creator of React Router suggests using the <a> tag.
I haven't tried it but syntactically you could do it like this:
<Route
path="/someroute2"
render={() => <Redirect to="http://anotherhost/anotherroute5" />}
/>

React Router V4 protected private route with Redux-persist and React-snapshot

I'm implementing private route like so using React Router Route Component:
function PrivateRoute({component: Component, authed, emailVerified, ...rest}) {
return (
<Route
{...rest}
render={props =>
authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}/>
)
}
Expected Behavior:
authed is persisted through page refresh through using redux-persist
So on page refresh or reload, if authed prop is true then router should render <Component /> without ever going to path "/"
Actual Behavior which is the Problem:
With authed === true (persisted)
reloading the page or refreshing it leads to the following actions taking place(checked redux devtools)
the action:
"##router/LOCATION_CHANGE" runs and takes it to the correct secure route but then "##router/LOCATION_CHANGE" runs again and it redirects to "/" for a moment and finally
"##router/LOCATION_CHANGE" runs again and directs route back to the secure path, even though authed === true through all this in the redux devtools
Then: My guess was that this error has something to with my main App Component rendering before redux-persist has time to re-hydrate the Redux store.
So I tried doing the following:
I tried delaying my main App component render until my store is re-hydrated using redux-persist like so:
render() {
const {authed, authedId, location, rehydrationComplete} = this.props
return (
<div>
{ rehydrationComplete
? <MainContainer>
<Switch key={location.key} location={location}>
<Route exact={true} path='/' component={HomeContainer} />
<Route render={() => <h2> Oops. Page not found. </h2>} />
</Switch>
</MainContainer>
: <div>...Loading </div> }
</div>
)
}
This effectively fixes the issue above of the path changing when "##router/LOCATION_CHANGE" action runs(only Changes the path keys), However this leads to another Issue with React-snapshot Now: all the static generated html files from react-snapshot Now contain only ...Loading. I tried to set snapshotDelay of 8200 in the react-snapshot options but that didnt solve the issue.
Then:
I tried the following to delay React-snapshot call so that it renders html after the store has been rehydrated:
import {render as snapshotRender} from 'react-snapshot'
import {ConnectedRouter} from 'react-router-redux'
async function init() {
const store = await configureStore()
snapshotRender(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
}
init()
But now i get the error: that 'render' from react-snapshot was never called. Did you replace the call to ReactDOM.render()?
I know this is a loaded question, but I want to effectively use these 3 libs(React-Router V4, Redux-persist, React-snapshot) together to serve protected routes without the mentioned errors.
I have something similar to you. Here I use React-Router V4 and a persist-like library.
Your router/routes doesn't need to be aware of the persist. They should rely on your redux's store. The persist should rehydrate your store with all the data.
I didn't see where you are using the PrivateRoute component in your example. Where is it?

Categories

Resources