React 18 [MainLayoutRoute] is not a <Route> component - javascript

I use the MainLayout component to add the Navbar to the required pages and redirect the user to the login page if he's not logged in.
However now since I updated to react 18 and Component in Route was replaced by element. this part is no longer working and threw an error
Uncaught Error: [MainLayoutRoute] is not a <Route> component.
All component children of <Routes> must be a <Route> or <React.Fragment
const MainLayoutRoute = ({ path, component: Component, render, ...rest }) => {
return (
<Route
path={path}
{...rest}
render={(props) => {
if (!auth.BasicAuth())
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location },
}}
/>
);
return Component ? (
<>
<Navbar />
<Component {...props} />
</>
) : (
render(props)
);
}}
/>
);
};

A Route component is used in React applications to define a path that the app should respond to. When the app’s URL matches the path specified by the Route, the component that is associated with that route will be rendered. This allows developers to create dynamic, single-page applications that can render different content based on the current URL.
On the other hand, MainLayoutRoute is not a Route component. It is a component that is used to wrap other components and provide them with a common layout. This is useful when building applications with multiple pages, as it allows the developer to define a consistent layout that can be applied to all pages.
Find more info here - https://moderncodr.com/react-18-mainlayoutroute-is-not-a-route-component/

Related

Navigating to a 404 Route with Reach Router

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.

reactjs props getting lost in Route creation

I'm attempting to pass a user's auth state down to components via a react-router-dom switch block (I believe I ought to look at implementing redux but that's a question for later).
There's a Home view that gets passed all the login information after the user authenticates, and I can see the authState object as a prop in the home component using react devtools:
import React from "react";
import Dashboard from "../Dashboard";
import {Switch, Route} from "react-router-dom";
import NoMatch from "../NoMatch";
function Home(props) {
// authState exists
return (
<Switch {...props}>
// authState exists
<Route exact path="/" render={(...props) => <Dashboard {...props} />} /> // authState gone
<Route component={NoMatch} />
</Switch>
);
}
export default Home;
after executing this it renders the Dashboard, and tracing down the component chain with react devtools I can see that the prop has been passed from the Home component to the Switch component successfully. However, once it gets to the Route the prop is gone, and the only props available in the Dashboard component are the history, location and match props.
Why are the props missing and what's the right way to pass them down?
Couple of improvements needed in your code:
Passing props to Switch component is unnecessary
No need to collect router props using the rest syntax only to spread them later
Main Problem:
props inside the function passed to render prop refers to the router props instead of the props passed to Home component. You are using the same identifier for two different things.
Solution
Use different names for router props and the props passed to Home component that you want to pass down to Dashboard component
function Home(props) {
return (
<Switch>
<Route
exact
path="/"
render={(routerProps) => <Dashboard {...routerProps} {...props} />}
/>
<Route component={NoMatch} />
</Switch>
);
}
Alternatively, if you don't need access to router props in the Dashboard component, remove the props parameter from the render prop function.
<Route
exact
path="/"
render={() => <Dashboard {...props} />}
/>
Now, you won't have access to router props inside the Dashboard component but the auth state of the user will be passed down to Dashboard component.
In the most recent versions of the react-router-dom you must replace render and component attributes with element. You cannot pass a callback function there in which there were specified the route props anymore.
Instead you can use a hook in your routed component:
const params = useParams();
to obtain the parameters.
See more in the official documentation.

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.

Programmatically creating routes from object does not work when doing it within a component

I am attempting to create a standard component for my application that returns a list of compatible routes with paths and children components systematically derived from an object featuring all of my application pages (eg. /log-in).
The issue is, I can place the route-returning script directly into the root react component, but when breaking that same code out into its own component and rendering in the root, it does not return any children, even though it returns the same thing.
This may have something to do with React route match, but I am not sure.
Example:
The object with all my pages:
...
const views = [
{
path: "/auth/sign-up",
exact: true,
component: SignUp,
secure: true
},
{
path: "/auth/sign-in",
exact: true,
component: SignIn,
secure: true
},
]
The Viewer component meant to catch the paths and return the compatible routes:
As an example, I am filtering based on whether the value of secure is true or not from the object above.
function Viewer(props) {
return (
views.filter(view => view.secure === props.secure).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
))
)
}
My root react component, featuring the Viewer component (From above), which does not work:
export default function Root() {
return (
<switch>
<Viewer secure={true} />
</switch>
);
}
...
My root react component where the routing code is placed directly inside, which does work:
export default function Root() {
return (
<Switch>
{views.filter(view => view.secure === true).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
))}
</switch>
);
}
...
Can you please assist me in understanding the difference between these two examples, the one using the routing code placed directly in the root element versus the root where it features the Viewer component which should do the same thing. Any info you can spare would be appreciated.
The cause of your issue is related how react-router-dom works, I mean Switch component is intended to render just one Route component at time, so the children for Switch component must be directly a Route or Redirect component instead of a wrapper for that Routes, if you want to do something like that with a Viewer component, you should move your Switch component inside your Viewer component, something like this
function Viewer(props) {
const routes = views.filter(view => view.secure === props.secure).map((view, index) => (
<Route
key={index}
path={view.path}
exact={view.exact}
children={<view.component />}
/>
));
return <Switch>{routes}</Switch>;
}
Here you have the official documentation that tells you about that and explain how it works.
Switch component will iterate over all its children to figure out if the Route should be render or not related to the url path, so for that reason is mandatory that the direct children of a Switch component must be Route or Redirect components cause these contains all the props , path is one of these props, so it is used to match the current url path to be compared with the path prop of the Route component.

How to make component not re-render when navigating pages?

In my application there is a main home page that gets loaded and there is another page (Page2.js) I can navigate to via the navbar I have implemented. The problem is that when I go to Page2.js and then go back to the main page, it will re-render all of the components on the main page. I am wondering if there is a way to check the last pathname and say if it is equal to a certain value then all of the components should stay the same?
I have tried using the shouldComponentUpdate method in the navbar (since I do not want it to change) and it is pretty faulty in that it would not read prevProps.location as a value but undefined:
shouldComponentUpdate = (prevProps) => {
if (this.props.location !== prevProps.location) {
return false;
}
}
App.js holds the code for my routing in the application:
// imports here
class App extends Component {
render() {
const {location} = this.props;
return (
<Switch >
<DefaultLayoutRoute exact path="/">
<Redirect to={HomePage}/>
</DefaultLayoutRoute>
<Route exact path={SIGNOUT} render={() => {
auth0Client.signOut();
window.location.href = homeRedirect;
return null
}}>
</Route>
<Route exact path={LOGIN_SUCCESS} component={Callback}/>
<DefaultLayoutRoute exact path={HomePage} location={location.pathname} component={HomePage}
pageName="Office Production"/>
<DefaultLayoutRoute
exact
path={AGENTRANKING}
component={AgentRankings}
pageName="Agents"
/>
<DefaultLayoutRoute exact path={SETTINGS} component={Settings}/>
</Switch>
);
}
}
export default withRouter(App);
Expected Result: To be able to navigate to Page2.js and then back to the main page with all of the components rendered the way it was unless I manually refresh
Actual Results: I go back to the main page and all the components start to re-render.

Categories

Resources