In react router V6, we write the routes in this fashion-
<BrowerRouter>
<Routes>
<Route path="..." element={<div> ... </div>} />
<Route path="..." element={<div> ... </div>} />
</Routes>
</BrowerRouter>
Now if I want to insert an element which depends on props, say an array of names (which is defined as another component) then in react router's older versions, it was possible to pass props to the element using inline function but my question is that how we can do the same in V6?
If you're referring to just random props provided to the component at this level you can just do this:
<BrowerRouter>
<Routes>
<Route path="..." element={ <div> <MyComponent namesList={["John Doe"]} /></div> } />
<Route path="..." element={ <div> ... </div> } />
</Routes>
</BrowerRouter>
If you want to make use of the router props inside your components you can do so using the useLocation, useNavigate or useParams hooks within your component for this.
Another option is to create a HOC like this:
import {
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
Then use it on your component like this:
const MyComponent = (routerProps) => (...)
export default withRouter(MyComponent)
Related
This worked in "react-router-dom": "^5.3.0"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Api from "./Api";
const api = new Api();
const App = () => {
return (
...
<Router basename="/my-app">
<Switch>
<Route
path="/complete"
render={(props) => <ConfirmationPage {...props} api={api} />}
/>
...
</Switch>
</Router>
After upgrading to "react-router-dom": "^6.4.3"
We've tried:
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Api from "./Api";
const api = new Api();
const App = () => {
return (
...
<Router basename="/my-app">
<Routes>
<Route
path="/complete"
element={(props) => <ConfirmationPage {...props} api={api} />}
/>
...
</Routes>
</Router>
But that doesn't work. We've read through https://reactrouter.com/en/6.4.3/upgrading/v5
but do not see how to handle passing in props.
In react-router-dom#6 the route components are passed as JSX to the element prop, and passing props to the routed component works just like it does anywhere else in React.
Example:
<Router basename="/my-app">
<Routes>
<Route
path="/complete"
element={(
<ConfirmationPage
api={api} // <-- props passed to component
/>
)}
/>
...
</Routes>
</Router>
There are no longer any route props, so if the routed components need access to what was previously provided they will need to use the React hooks, i.e. useLocation, useNavigate, useParams, etc.
Additional documentation:
Why does <Route> have an element prop instead of render or component?
Advantages of <Route element>
<Route path="users">
<Route path=":id" element={<UserProfile id={":id"} />} />
</Route>
The above is what I am trying to do. The code isn't correct. How to make it correct?
The route path params are located on a params object accessible by the useParams hook.
The useParams hook returns an object of key/value pairs of the
dynamic params from the current URL that were matched by the <Route path>. Child routes inherit all params from their parent routes.
For a given route:
<Route path=":id" element={<UserProfile />} />
The UserProfile component will use the useParams hook to access the id route path param.
Example:
import { useParams } from 'react-router-dom';
...
const UserProfile = () => {
...
const { id } = useParams();
...
};
If the UserProfile component can't use React hooks directly for some reason and takes id as a prop, then create a wrapper component to use the hook and inject the prop.
Example:
import { useParams } from 'react-router-dom';
...
const UserProfileWrapper = () => {
...
const { id } = useParams();
...
return <UserProfile id={id} />;
};
...
<Route path="users">
<Route path=":id" element={<UserProfileWrapper />} />
</Route>
I created <RequireAuthRoute> which simply either returns its children or navigates to /login. However the way it is being used doesn't satisfy me. Take a look at this fragment:
<Route
path=''
element={
<RequireAuthRoute>
<Explorer />
</RequireAuthRoute>
}
/>
So yes - technically it works but what I wanted to do is to create wrapper for <Route> component so it would end up looking like this:
<ProtectedRoute path='' element={<Explorer/>}/>
Whats blocking me is react-router itself which tells me that <Router> direct child can only be <Route> component. Any workarounds?
Custom route components aren't valid in react-router-dom#6 like what was commonly used in v5.
<ProtectedRoute path='....' element={<Explorer />} />
The above will throw an invariant violation because only Route and React.Fragment are valid children of the Routes component, and Route components can only be rendered directly by the Routes component or another Route component.
If using wrapper components such as RequireAuthRoute is insufficient:
<Route
path='....'
element={
<RequireAuthRoute>
<Explorer />
</RequireAuthRoute>
}
/>
Then the other common v6 pattern is to use layout routes that render an Outlet component for nested routes instead of a children prop.
Example:
import { Navigate, Outlet, useLocation } from 'react-router-dom';
const AuthLayout = () => {
const location = useLocation();
... business logic ...
return isAuthenticated
? <Outlet />
: <Navigate to="/login replace state={{ from: location }} />
};
Then wrap and render the routes you want to protect with this layout route:
<Route element={<AuthLayout />}>
<Route path='....' element={<Explorer />} />
... other protected routes ...
</Route>
... other unprotected routes ...
Two options:
Option 1. Use a curried function to wrap each lazy loaded Route's element in a Suspense component. This avoids having to do so in each Route's element prop.
import { Suspense } from "react";
export const Loadable = (Component: any) => (props: any) => {
return (
<Suspense fallback={<p>Loading</p>}>
<Component {...props} />
</Suspense>
);
};
Option 2: Declare routes via data instead of using the Route component. Use a custom function (createRoutesWithSuspenseWrappers in this example) to generate each Route, wrapped in Suspense.
const routes = createRoutesWithSuspenseWrappers([
{ path: '/', element: <Root /> },
// ...
]);
createBrowserRouter(routes);
Is there a way to pass the location prop and own made prop to another component? I've figured out how to pass DIR_URL through a function like below but I also need to use location prop later in ConfirmAccount component to read pathname property and so on. (Of course in this way it gets true value).
import React, { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Main from './components/structure/Main';
import ConfirmAccount from './components/pages/ConfirmAccount';
import NoMatch from './components/pages/NoMatch';
class App extends Component {
render() {
const url = 'http://localhost:3006';
return (
<Fragment>
<Router>
<Switch>
<Route exact path="/" component={Main} />
<Route path="/confirm">
{/* How can I pass the location? */}
<Route path="/:url" component={() => <ConfirmAccount DIR_URL={url} location />} />
</Route>
<Route component={NoMatch} />
</Switch>
</Router>
</Fragment>
);
}
}
export default App;
React Router DOM automatically passes match location and history props.
You can use the route render prop to pass them manually if you wish:
<Route path="/:url" render={(routeProps) => <ConfirmAccount DIR_URL={url} {...routeProps} />} />
I suggest that you use useHistory hook from ReactRouterDom inside your child component. There you got all the location stuff that you need.
Or pass route properties to rendering component:
import React, { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route, useHistory } from 'react-router-dom';
import Main from './components/structure/Main';
import ConfirmAccount from './components/pages/ConfirmAccount';
import NoMatch from './components/pages/NoMatch';
class App extends Component {
render() {
const url = 'http://localhost:3006';
return (
<Fragment>
<Router>
<Switch>
<Route exact path="/" component={Main} />
<Route path="/confirm">
{/* How can I pass the location? */}
<Route path="/:url" component={(routeProps) => <ConfirmAccount DIR_URL={url} {...routeProps} />} />
</Route>
<Route component={NoMatch} />
</Switch>
</Router>
</Fragment>
);
}
}
const ConfirmAccount = ({location}) => {
const history = useHistory()
}
export default App;
just import { useLocation } from 'react-router-dom' and use it like this:
const location = useLocation()
now you can access the location object.
read more about it here: https://reactrouter.com/web/api/Hooks/uselocation
or you can use withRouter HOC like this https://reactrouter.com/web/api/withRouter
Hi I have a react (GatsbyJs) app where i am using dynamically fetched data and authentication. I have a PrivateRoute component that checks whether the user is logged in and then redirects to the component or to the login page depending on the status.
I now need to pass props to components but couldn't master it.
Here is the PrivateRouter:
import React from 'react'
import { navigate } from 'gatsby'
import { isLoggedIn } from '../services/auth'
const PrivateRoute = ({ component: Component, location, ...rest }) => {
if (!isLoggedIn() && location.pathname !== '/app/login') {
navigate('/app/login')
return null
}
return <Component {...rest} />
}
export default PrivateRoute
And the app.js code:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import OrderDetails from "../modules/order-details"
import ItemDetails from "../modules/item-details"
import ProductionOrders from "../modules/production-orders"
import ProdOrderDetail from "../modules/production-order-detail"
import CardDetail from '../modules/card-details'
import Cards from "../modules/cards"
import Orders from "../modules/orders"
import Items from "../modules/items"
import PrivateRoute from '../components/privateRoute'
import Profile from '../components/profile'
import Login from '../modules/login'
import ForgotPassword from "../modules/forgotPassword"
import NewPassword from "../modules/newPassword"
import Invoices from "../modules/invoices"
import Dispatches from "../modules/dispatches"
import InvoiceDetails from "../modules/invoice-details"
import OrderPlan from "../modules/order-plan"
import AccountStatementPage from "../modules/acc-statement"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<PrivateRoute path="/order-details/:orderId" component={OrderDetails} />
<PrivateRoute path="/item-details/:itemId" component={ItemDetails} />
<PrivateRoute path='/production-orders' component={ProductionOrders} />
<PrivateRoute path="/production-order-detail/:companyNr/:orderId" component={ProdOrderDetail} />
<PrivateRoute path="/cards" component={Cards} />
<PrivateRoute path="/card-details/:cardId" component={CardDetail} />
<PrivateRoute path="/orders" component={Orders} />
<PrivateRoute path="orders/cId/:cId" component={Orders} />
<PrivateRoute path="orders/keyword/:keyword" component={Orders} />
<PrivateRoute path="/items" component={Items} />
<PrivateRoute path="/items/keyword/:keyword" component={Items} />
<Login path="/login" />
<ForgotPassword path="/forgot-password" />
<NewPassword path="/new-password" />
<PrivateRoute path="/profile" component={Profile} />
<PrivateRoute path="/invoices/" component={Invoices}/>
<PrivateRoute path="/invoices/cId/:cId" component={Invoices}/>
<PrivateRoute path="/dispatches/" component={Dispatches}/>
<PrivateRoute path="/dispatches/cId/:cId" component={Dispatches}/>
<PrivateRoute path="/invoice-details/:invId" component={InvoiceDetails} />
<PrivateRoute path="/order-plan" component={OrderPlan} />
<PrivateRoute path="/acc-statement/:id" component={AccountStatementPage}/>
</Router>
</Layout>
)
}
export default App
How should I rework them to be able to pass down props to the Component?
Thanks in advance.
P.S.
This is the component:
import React from 'react'
import Container from "react-bootstrap/Container"
import Col from 'react-bootstrap/Col'
import Row from 'react-bootstrap/Row'
import AccStatement from '../components/accStatement'
const AccountStatementPage = (props,{location}) => {
console.log(location)
console.log(props)
return (
<Container fluid>
<h1>Cari Hesap Ekstresi</h1>
<Row className="h-100">
<AccStatement id={props.id} />
</Row>
</Container>
)
}
export default AccountStatementPage
Your private route component is already configured to pass on any additional props passed to it from the router, but since you are trying to send additional data to the rendered component you need to send the route state correctly.
Link
<Link to=“...” myState=“....” >
You can access route state from the location prop passed to each rendered component.
const MyComponent = ({ location }) => {
return (
<div>My route state: {location.state.myState}</div>
);
};
If your component doesn't receive the route props then you can use the useLocation react hook.
const MyComponent = () => {
const location = useLocation();
return (
<div>My route state: {location.state.myState}</div>
);
};
How should I access it in the target component?
location should be injected into the props object passed to your component by the Route. It isn't a separate argument. The route params are also placed on the match prop and are not a root-level prop value.
Given
<PrivateRoute
path="/acc-statement/:id"
component={AccountStatementPage}
/>
Component
const AccountStatementPage = ({ location, match }) => {
useEffect(() => {
// Log route state and match param `id` on mount
console.log(location.state, match.id);
}, []);
return (
<Container fluid>
<h1>Cari Hesap Ekstresi</h1>
<Row className="h-100">
<AccStatement id={match.id} />
</Row>
</Container>
)
};
You're really close.
This is working for me. Pass your state object inside Link per the Gatsby spec. Then you need to access your Router props (which contains the location object which has your Link state) and pass location.state.example to a component like so.
Link
<Link
className="link-wrapper"
to={endpoint}
state={{ example: 'something' }}
>
RouterPage (PrivateRouter)
const RouterPage = (
props: { pageComponent: JSX.Element } & RouteComponentProps
): JSX.Element => {
if (!loggedIn) {
navigate('/404')
}
return props.pageComponent
}
Router
const App: React.FC<RouteComponentProps> = (props: any) => {
return (
<Router className="below-header-container">
<RouterPage
path={withPrefix('/some-path/create')}
pageComponent={<CreateSomething />}
/>
<RouterPage
path={withPrefix('/some-path/:id')}
pageComponent={
<EditSomething name={props.location.state.example} />
}
/>
<RouterPage
path={withPrefix('/some-path')}
pageComponent={<Something />}
/>
</Router>
)
}
Component
const EditSomething: React.FC<any> = ({ name }) => {
return (
<section className="edit-something">
<div className="row">
<h1>{name}</h1>
</div>
</section>
)
}
In your PrivateRoute component you are destructuring the component as a Component (to render it), a location, and the rest (...rest) for rest of the props.
To pass a custom prop to your component inside PrivateRoute, you just need to access directly your prop:
In your app.js:
<PrivateRoute path="/order-plan" component={OrderPlan} customProp={`hello`}/>
Your customProp is passing to your component through rest spread operator in PrivateRoute so, in your component, just access to props.customProp.
If you want to pass something using Link component, you should use the state provided by Gatsby Link itself (because Gatsby extends from #reach/router):
<Link
to={`/order-plan`}
state={{ yourVariable: 'hello' }}
>
Then, on your order-plan page you have to access to props.location.state.yourVariable to get your data.
I couldn't access the route state through location. It was undefined all the time.
But I could access the route state through:
window.history.state.customProps
Thanks for your support, be well.