How to get params in component in react router dom v4? - javascript

I have this code:
<BrowserRouter>
<Route path="/(:filter)?" component={App} />
</BrowserRouter>
the filter param or '' on the root is suppose to be on App components' props base on the previous react router versions?
This is my code on my App:
const App = ({params}) => {
return ( // destructure params on props
<div>
<AddTodo />
<VisibleTodoList
filter={params.filter || 'all'} // i want to git filter param or assign 'all' on root
/>
<Footer />
</div>
);
}
I logged this.props.match.params on console but it has none? help?

I assume you are following the Redux tutorial on Egghead.io, as your example code seems to use what is defined in that video series. I also got stuck on this part trying to integrate React Router v4, but eventually found a working solution not far from what you have tried.
⚠️ NOTE: one thing I would check here is that you are using the
current version of react-router-dom (4.1.1 at the time of this
writing). I had some issues with optional filters in the params on
some of the alpha and beta versions of v4.
First, some of the answers here are incorrect, and you indeed can use optional params in React Router v4 without the need for regular expressions, multiple paths, archaic syntax, or using the <Switch> component.
<BrowserRouter>
<Route path="/:filter?" component={App} />
</BrowserRouter>
Second, as others have noted, React Router v4 no longer exposes params on route handler components, but instead gives us a match prop to use.
const App = ({ match }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={match.params.filter || 'all'} />
<Footer />
</div>
)
}
From here, there are two ways to keep your content in sync with the current route: using mapStateToProps or using the HoC withRouter, both solutions are already talked about in the Egghead series so I won't recapitulate them here.
If you are still having trouble, I urge you to check out my repository of the completed application from that series.
Here is the commit using the mapStateToProps solution
Here is the commit using the withRouter soluiton
Both of which seem to work fine (I just tested both of them).

React Router v4 does not accept a regex for the path. You won't find regular expressions covered anywhere in the documentation. Instead of a regex you can just create multiple routes inside the <Switch> component and the first one that matches will be rendered:
<BrowserRouter>
<Switch>
<Route path="/" component={App} />
<Route path="/:filter" component={App} />
</Switch>
</BrowserRouter>
You also have a bug in your App component. You get the params via the match property, not the params property (not tested, but this should be closer to what you want):
const App = ({match}) => {
return (
<div>
<AddTodo />
<VisibleTodoList
filter={match.params.filter || 'all'}
/>
<Footer />
</div>
);
}
All of the above is covered in the React Router V4 docs on ambiguous matches

From the react-router documentation, props.match.params is where your parameteres are stored.
So to access the filter name, try this
const App = ({match}) => {
...
<VisibleTodoList
filter={match.params.filter || 'all'}
/>
...
}

I know the question is about v4, but if sm looks for v5, we can use the useParams hook.
// /:filter
const {filter} = useParams();

To get the params in the path URL
//to your route component
<Route path={`${match.url}/upload/:title`} component={FileUpload}/>
=====
//Parent component
<Link to={`${match.url}/upload/css`}>
=====
//Child component
const {params: {title}} = this.props.match;
console.log(title);
result = css

I do not know why react router cannot detect my filter even though it's working properly, I resolved this problem by using location.pathname since it does the work for me. I think react router cannot detect my filter param because of regexp, I expected that so I put || to use all on root, but unfortunately for me I it cannot detect so I used location.pathname . I would appreciate suggestions on this since I am not sure with my path configuration on regexp.

Related

react-router v6 wrapped route component not working [duplicate]

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>} />

Passing props to dynamically rendered components in React

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
)

<Switch> component matching null value in react-router-4

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

react router this.props.location

I need a help with react-router v2+
I have to change class of navbar when route changed
for example for route /profile className will be "profile-header"
I tried to use this.props.location in navbar component but it shows undefined
Hope your help
Your navbar component (as you described it in your question) is probably not the route component, right? By route component I mean the one that you use in your react-router configuration that is loaded for a specific route.
this.props.location is accessible only on such route component, so you need to pass it down to your navbar.
Let's take an example:
Your router config:
<Router>
<Route path="/" component={App}>
// ...
</Router
Route component App:
class App extends React.Component{
// ...
render() {
return <Navbar location={this.props.location}/>
}
}
There could be a scenario where you may not have access to props.location to pass to the nav component.
Take for example - We had a header component in our project which was included in the routing switch to make it available to all routes.
<Switch>
<Fragment>
<Header/>
<Route path='..' component={...}/>
<Route path='..' component={...}/>
</Fragment>
</Switch>
In the above scenario there is no way to pass the location data to the Header component.
A better solution would be to us the withRouter HOC when a component is not being rendered by your router.
You will still have access to the router properties history, match and location when you wrap it in the withRouter HOC:
import { withRouter } from 'react-router-dom'
....
....
export default withRouter(ThisComponent)
react-router v4
From documentation:
<Route> component property should be used, when you have an existing component. <Route> render property takes an inline function, that returns html.
A <Route> with no path will always match.
Based on this, we can make a <Route> wrapper to any level of your html structure. It will always be displayed and have access to the location object.
As per your request, if a user comes to /profile page, the <header> will have profile-header class name.
<Router>
<Route render={({ location }) =>
<header className={location.pathname.replace(/\//g, '') + '-header'}>
// header content...
</header>
<div id="content"></div>
<footer></footer>
} />
</Router>
I couldn't solve it with the solutions given here and here is what worked for me:
I imported history into my component and assigned history.location.pathname to a variable which I later used for dynamic style manipulation.
In case you are rendering the component with pre-defined location.state values, first set your state with props.location.state then use your state data in your elements.

React Router Param

Is it possible to do this with react router?
<Route path="/:reset_password" component={ResetPassword} />
<Route path="/:create_password" component={CreatePassword}/>
I want use different param with different component. I test above code and it doesn't work. the code above work if I change to this:
<Route path="/something1/:reset_password" component={ResetPassword} />
<Route path="/something2/:create_password" component={CreatePassword}/>
thanks for the help
As #chris said, remove the colons and/or assign dedicated routes. React Router can't differentiate between the two routes you've supplied, as they're technically identical (root path + a dynamic parameter.)

Categories