I have App.js with route
<Route path="/home" component={() => <Home changeAuth={this.changeAuth} auth={auth} />}/>
But when i use component to display modal with:
<Link to="/home/info"> <span class="logout">App Info</span></Link>
<Route path="/home/info" component={Info} />
Component was re-render and run componentDidMount.
It's not when i use <Route component ={Home}/> but i want using props.
You should use Route render prop, not component in this case:
<Route path="/home" render={() => <Home changeAuth={this.changeAuth} auth={auth} />}/>
PS: this depends on the version of react-router that you are using, so I'm assuming you are using v4
From the Route Component Documentation:
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).
Related
This question already has answers here:
Error: [PrivateRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
(18 answers)
Closed last year.
How can you render a composed Route component
code example
Bottomline from above example is that in the following code, the Wrapped route will never render it's element
const App = () => (
<Routes>
<Wrapped/>
<Route path="/inline" element={<span >Inline works</span>} />
</Routes>
);
const Wrapped = () => <Route path="/wrapped" element={<span>wrapped</span>} />
Is there a way of doing this kind of composition with the Route component with react-router v6? Or will react-router v6 only support Route directly nested in the Routes component?
Edit, more specifically I'm trying to get a recommendation for using a ProtectedRoute component, something among the lines of:
type Props = {
element: ReactElement;
redirectRoute: string;
} & RouteProps;
const ProtectedRoute = ({element, redirectRoute, ...rest}: Props) => {
const {isAuthenticated} = useAuth();
<Route {...rest} element={isAuthenticated() ? element : <Navigate to={redirectRoute}/>}/>
}
EDIT:
It seems like this used to work in older beta versions, so this might be a bug. At the moment the latest version is 6.0.0-beta.4 &
I've logged an issue:
https://github.com/remix-run/react-router/issues/8066
In your code, you are trying to use Wrapped Component as a Router, but it's not. It's a React element returning a React Router element. Since you only need Router in this simple usecase, you can treat is as a function:
<Suspense fallback={null}>
<Routes>
{/* Use this as a normal function, and also function name etc.,*/}
{Wrapped()}
<Route path="/inline" element={<span>Inline works</span>} />
</Routes>
</Suspense>
However, I would recommend not to complicate the routes by trying to add customizations on route and instead wrap your component that you want to route.
For eg.,
const Wrapped = () => <Route path="/wrapped" element={<span>wrapped</span>} />;
to
<Route path="/wrapped" element={<Wrapped>wrapped component</Wrapped>} />
Or will react-router v6 only support Route directly nested in the Routes component?
Correct, RRv6 does not support route composition. Instead, try using your <Wrapped /> component inside the element prop. E.g.
<Route path="/foo" element={<Wrapped>/* something here */</Wrapped>} />
I have a page that is displaying several of my star components that each have their own name and a prop called starType
I am generating several different of these stars with the following code
if (num > 0) {
return (
<div className="starWrapper">
<Star
name={`${makeid()}`}
starType={`${starList[Math.floor(Math.random() * 6 + 1)]} ${posList[Math.floor(Math.random() * 9 + 1)]}`}
></Star>
{makeStars((num - 1))}
</div>
);
And this is the star component
<NavLink to={props.name}>
<h1 className="star-label">{props.name}</h1>
<div className={``}>
<div className={`starBall ${props.starType}`}></div>
</div>
</NavLink>
At the moment I want the user to be able to click on each star and have it lead to a page. I have achieved that with react-router's dynamic routing
<Route
exact
path="/:id"
render={(props) => <GenerateSystem {...props} />}
/>
the issue is I want the page that is generated from my generateSystem component to have the starType prop passed to it by the star component. I am aware of React's one way data flow and I think that might be the issue. How can I pass prop data from an auto generated component to another auto generated component?
My full code is viewable here. The components I'm talking about are in the interstellar-view and systems folder.
since you are passing name through URL params so passing starType using query params is an easy option.
So URL would look like this www.example.com/123?starType=red-giant
In your star.jsx, make a modification like this
<NavLink to={`/${props.name}?starType=${props.starType}`}>
...
</NavLink>
In your App.js, make a modification like this
<Switch >
<Route exact path="/:id" component={GenerateSystem} />
<Route exact path="/sol" component={SolSystem} />
<Route exact path="/" component={Interstellar} />
</Switch>
(We do not need to render and pass props since we can use useParams in GenerateSystem.js)
In your GenerateSystem.js, make a modification like this
import React from "react";
import { Link, useLocation, useParams } from "react-router-dom";
function useQuery() {
return new URLSearchParams(useLocation().search);
}
export const GenerateSystem = (props) => {
const {name} = useParams();
const query = useQuery();
const starType = query.get('starType')
return(<div className={starType}>System <p>{name}</p></div>)
}
Refs:
https://reactrouter.com/web/api/Hooks/useparams
https://reactrouter.com/web/example/query-parameters
EDIT:
You can use Redux-store/Context-API to have a global store, so that name and starType can be stored globally and can be accessed in different components
More Use-cases Example -> for other people that came here:
As in React-Router-Dom V6-> there is no render method any more,
See Why does have an element prop instead of render or component?
We mentioned this in the migration guide from v5 to v6, but it's worth repeating here.
In React Router v6 we switched from using v5's and APIs to . Why is that?...
So I needed another way of dynamically rendering all routes for the Router, with a pre declared array with all routes:
const routingList = [{title: 'Home', search: '/', component: Home, icon: 'fa-home'},{...}]
<Routes>
{
routingList.map((routing) => {
let Child = routing.component;
return <Route key={routing.search} path={routing.search} element={<Child {...routing.compProps} />} />;
})
}
<Route path="*" element={<Notfound />} />
</Routes>
(BTW: if you also need the useLocation or the other hooks, and you are using React Class and not React functions, see my answer here:
Component with router props - For: Hooks can only be called inside of the body of a function component
)
I used same component for different routes. When route changes, I want the component to be rendered.
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/hotels" component={HotelsPage} />
<Route path="/apartments" component={HotelsPage} />
</Switch>
When I change the route path from /hotels to /apartments, the component HotelsPage doesn't refresh.
What is the cool approach for this?
One of the ways you can get this sorted is by passing the props explicitly like :
<Route path="/hotels" component={props => <HotelsPage {...props} />} />
Firstly you can aggregate the Route into one like
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/(hotels|apartments)" component={HotelsPage} />
</Switch>
and secondly, your HotelsPage component is rendered both on /hotels, /apartments, it is similar case like path params, whereby the component doesn't mount again on path change, but updates thereby calling componentWillReceiveProps lifecycle function,
What you can do is implement componentWillReceiveProps like
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname !== this.props.location.pathname) {
console.log("here");
//take action here
}
}
DEMO
I guess just passing useLocation().pathname will resolve issue
useEffect(
() => {
// Your logics
});
}, [useLocation().pathname])
I just noticed that in react router (v3.x) a component unmounts and remounts if a path param changes. Is this the expected behaviour?
Route:
<Route path="/landing/register/:step" component={Register}/>
Now, lets say I am on route "/landing/register/personal-data" and I am navigating via <Link/> or router.push({...}) to the next registration step "/landing/register/address", the Register-component gets first unmounted and then mounted again, loosing all its state.
Is this the correct way or am I doing something wrong?
EDIT:
It seems that the problem is that I am using nested routes, where I use a component for the parent route.
This example works (not re-mounting Register-Comp on path param change):
<Route path="/landing">
<Route path="register/:step" component={Register}></Route>
</Route>
But when I use a component for the parent route, it doesnt (not re-mounting AppView-Comp, but Register-Comp on path param change):
<Route path="/landing" component={AppView}>
<Route path="register/:step" component={Register}></Route>
</Route>
I solve this problem by nesting routes in child components, like this:
// Router class
<Route path="/landing/register" component={Register}/>
//Register component
<BrowserRouter>
<div>
<Route path="/landing/register/personal-data" component={PersonalData}/>
<Route path="/landing/register/payment-data" component={PaymentData}/>
...other routes
</div>
</BrowserRouter>
But in this case i store user data in redux store instead of component state, however you can store it on your component state it is not problem.
I'm trying to migrate to use React Router 4 and having some trouble understanding the logic of the <Switch> component as it's used in the docs to handle a 404 (or unmatched) route.
For my entry JavaScript file, I have the following routes set up.
index.js
<Switch>
<Route path="/login" component={Login} />
<Route path="/forgot-password" component={ForgotPassword} />
<Route path="/email-verification" component={EmailVerification} />
<Route component={App} />
</Switch>
The Login component will check to see if the user is authenticated, and if so, redirect the user to the /dashboard route (via history.replace).
The App component is only accessible when the user is authenticated and it has a similar check to redirect the user to /login if she is not.
In my App component I have more specified routes that I can be sure are only accessible if the user is logged in.
App.js
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/accounts" component={Account} />
<Authorize permissions={['view-admin']}>
<Route path="/admin" component={Admin} />
</Authorize>
<Route path="/users" component={Users} />
<Route component={NotFound} />
</Switch>
Herein lies my problem. The Authorize component checks against the permissions passed to see if the user has those permissions, if so, it renders the children directly, if not, it returns null from render().
The expected behavior here is that the <Route path="/admin" /> does not render at all when there are insufficient permissions and the <Route component={NotFound} /> component renders.
According to the docs:
A renders the first child that matches. A
with no path always matches.
However, if I go to any route declared after the <Authorize> component, the router is matching to null. This means that, based on the example above, going to /users returns null. Is the expected behavior of react-router to return the first match in a <Switch/> component, even if it's a null value?
How can I provide a "catch-all" route (404) for such a situation without creating a <PrivateRoute> component for each of the many, authenticated routes in App.js? Should a null value really produce a match?
Unfortunately, react-router's Switch component won't work with routes nested inside other components like in your example. If you check the docs for Switch, it says:
All children of a <Switch> should be <Route> or <Redirect> elements.
... so your Authorize component is not actually legal there as a direct child of Switch.
If you have a read through the source code of the Switch component, you'll see that it rather evilly reads the props of each of its children and manually applies react-router's matchPath method on each child's path (or from) prop to determine which one should be rendered.
So, what's happening in your case is Switch iterates through its children until it gets to your Authorize component. It then looks at that component's props, finding neither a path or from prop, and calls matchPath on an undefined path. As you note yourself, "a <Route> with no path always matches", so matchPath returns true, and Switch renders your Authorize component (ignoring any subsequent Routes or Redirects, since it believes it found a match). The nested '/admin' route inside your Authorize component doesn't match the current path however, so you get a null result back from the render.
I'm facing a similar situation at work. My plan to work around it is to replace react-router's Switch in my routing code with a custom component which iterates through its children, manually rendering each one in turn, and returning the result of the first one that returns something other than null. I'll update this answer when I've given it a shot.
Edit: Well, that didn't work. I couldn't work out a supported way to manually invoke "render" on the children. Sorry I couldn't give you a workaround to Switch's limitations.
In case anyone reads this in >= 2019, one way to deal with this behaviour is to simply wrap the Route-component like so:
import React from 'react'
import { Route } from 'react-router-dom'
type Props = {
permissions: string[]
componentWhenNotAuthorized?: React.ElementType
}
const AuthorizedRoute: React.FunctionComponent<Props> = ({
permissions,
componentWhenNotAuthorized: ComponentWhenNotAuthorized,
...rest
}) => {
const isAuthorized = someFancyAuthorizationLogic(permissions)
return isAuthorized
? <Route {...rest} />
: ComponentWhenNotAuthorized
? <ComponentWhenNotAuthorized {...rest} />
: null
}
export default AuthorizedRoute
Then, simply use it as such:
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import AuthorizedRoute from 'some/path/AuthorizedRoute'
import Account from 'some/path/Account'
import Admin from 'some/path/Admin'
import Dashboard from 'some/path/Dashboard'
import NotFound from 'some/path/NotFound'
import Users from 'some/path/Users'
const AppRouter: React.FunctionComponent = () => (
<Switch>
<Route
component={Account}
path='/accounts'
/>
<AuthorizedRoute
component={Admin}
componentWhenNotAuthorized={NotFound}
path='/admin'
permissions={['view-admin']}
/>
<Route
component={Dashboard}
path='/dashboard'
/>
<Route
component={Users}
path='/users'
/>
<Route
component={NotFound}
/>
</Switch>
)
export default AppRouter
Similar idea to what Robert said, here's how I did it
class NullComponent extends React.Component {
shouldComponentBeRenderedByRoute() {
return false;
}
render() {
return null;
}
}
class CustomSwitch extends React.Component {
render() {
return (
// React.Children.map returns components even for null, which
const children = React.Children.toArray(this.props.children).map(child => {
const { render, shouldComponentBeRenderedByRoute } = child.type.prototype;
if (shouldComponentBeRenderedByRoute && !shouldComponentBeRenderedByRoute.call(child)) {
return null;
}
if (shouldComponentBeRenderedByRoute) {
return render.call(child);
}
return child;
});
return <Switch>{children}</Switch>;
);
}
}
then use it just do
<CustomSwitch>
<Route path... />
<NullComponent />
<Route path... />
</CustomSwitch>
here, a component without shouldComponentBeRenderedByRoute function is assumed to be a valid Route component from react-router, but you can add more condition (maybe use path props) to check if it's a valid Route