React router v6 access route params and pass as props - javascript

In react router v6, how can I pass route params to a component without the need to use useParams() in the component?
This is what I want to do:
<Route
path='/'
element={ProfileComponent username={'thedefault'}
/>
<Route
exact
path='/u/:username/'
render={(props) =>
<ProfileComponent username={props.match.params.username}/>
}
/>
I don't want to put useParams() in the component because this tightly couples it to the URL. For example, what if I wanted to render another ProfileComponent elsewhere, with a different username to that in the URL. It seems to violate best practice for unit testing unless I can do it like my example.

I don't want to put useParams() in the component because this
tightly couples it to the URL. For example, what if I wanted to render
another ProfileComponent elsewhere, with a different username to that
in the URL. It seems to violate best practice for unit testing unless
I can do it like my example.
Any route using a username route match param would still be accessible via the useParams hook, but I think I understand what you are after. If I understand your question correctly you are asking how to map a route match param to a component prop in a generic way.
For this you can simply use a wrapper component to "sip" the route match param and pass it along to your component on any specific prop.
const ProfileComponentWrapper = () => {
const { username } = useParams();
return <ProfileComponent username={username} />;
};
...
<Route
path='/u/:username/'
element={<ProfileComponentWrapper />}
/>

In the docs, it is clearly specified that it is not possible
Normally in React you'd pass this as a prop: , but you don't control that information because it comes from the
URL.
https://reactrouter.com/docs/en/v6/getting-started/tutorial#reading-url-params
So, you have to use useParams in the component

Related

Getting Global Access to URL ID Params

How do I get global access to the current URL ID params? I'm having trouble getting access to the current URL ID in a child component in React. The ID is needed in order to query a MongoDB database in my ChecklistTool component.
Normally, I'd pass the props and get access to params that way. However, I'm using Editor.js which is not letting me pass the props as required.
This is my Editor.js component. It has access to params information:
<EditorJs
instanceRef={(instance) => (instanceRef.current = instance)}
placeholder="Start typing what's in your head..."
tools={EDITOR_JS_TOOLS}
enableReInitialize={true}
data={data}
/>
This is my custom Checklist component which is rendered as a block inside of the Editor.js component. I'm unable to pass the URL params to this component:
<ChecklistTool
onDataChange={onDataChange}
readOnly={this.readOnly}
data={this.data}
isAdmin={true}
/>
This is the file where I'm using React Router:
<Router>
<Switch>
<Route exact path="/document/:id" component={DocumentView}/>
</Switch>
</Router>
Any idea how to get access to the current URL params ID in all required files? Thanks!
If you are using a version of React >= 16.8 then you can use hooks.
Instead of using props to pass the value, React-Router provides a hook called useParams which enables you to access the URL params from within any component (as long as it is wrapped inside the router), which makes it essentially global and accessible from any child without passing as props.
This is how you use it:
First you import useParams
import {useParams} from "react-router-dom";
Then call the hook
const params = useParams(); //:id param will be in params.id and so on...
If you have tried this approach and did not work for you, let me know more about the kinds of errors you received, if any, and I will look into them.
React-Router reference for more information about useParams hook.

Issue with routing in React when user directly hits url that requires id from another component

I have this issue that I am facing in react related to routing.
There is this button which appears on the screen which appears only if the user is authenticated and thus it's a private route "/home/coupons".
When user clicks on any of the coupons ,I directed the user to
http://localhost:3000/coupons?id=6
Using this props.history:
this.props.history.push({
pathname: '/coupons',
search: '?id='+id,
})
I did the routing for this in my App.js:
<Route path="/coupons" component={CouponsRedeem}/>
The Component CouponsRedeem needs the id from the Component where users gets directed when he hits /home/coupons.
My concern is that if user directly hits the route /coupons without navigating to /home/coupons,the entire web app break since we don't get any get anything in this.props.location.
How do I make sure that such things don't happen?
Is my routing done correctly?
Any suggestions are most welcomed.
If it lacks information ,I will add more code snippets.
I am using react-router.
The solution to your problem is pretty simply. Either you have a default value that you use when the user didn't navigate from home/coupons to coupons since history.location.search doesn't have anything or you reconfigure your Route to use Router param where in instead of query your specify your Route like
<Route
exact
path="/coupons/:id"
component={Coupon.Show}
/>
and use it like
this.props.history.push({
pathname: `/coupons/${id}`,
})
or else if the coupon isn't received you can redirect the user to the home/coupons route
Looking at your implementation, I would assume that you're using either react-router or reach-router ?
If so, in my experience I tend to use route props to specify that an ID should be expected.
I would usually setup a route for the index and a route for showing an object and a default route for no matches (think of that like a 404) like so:
<Route
exact
path="/coupons"
component={Coupon.Index}
/>
<Route
exact
path="/coupons/:id"
component={Coupon.Show}
/>
<Route component={NoMatch} />
Specifying the "exact" prop ensures that it'll only render that component when the path matches correctly, so if you dont want an index route, you could omit it completely and when you navigate to that it'll fall back to the NoMatch component
The example I provided would change how it looks in the address bar for a more RESTful looking approach so instead of
http://localhost:3000/coupons?id=6 it would be http://localhost:3000/coupons/6
In the show component you can then access the prop value by the name that you specify after the colon ":id" in your Show component, using the props provided by the router like so:
let { id } = this.props.match.params;
I hope this helps

React extract param from url

I'm using react-router for routing . I've used NavLink and Route components like this:
<NavLink className={classes.navLink}
activeClassName={classes.activeNavLink}
to={`/brokers/${n.id}`}\>
....
<Route exact path="/brokers/:id" component={BrokerDetails} />
Now my question is - how do I use the id parameters passed in inside the BrokerDetails component ? I tried reading it via the props but it doesn't exist .
When using component=..., your component will be passed the route props.
In particular, you'll want to access the match object:
const BrokerDetails = ({match}) => <div>{JSON.stringify(match.params)}</div>;
should show you all the parameters; match.params.id would be the id parameter.
If you try to access props.Id won't work because it isn't in that location.
When you try to access params from an URL, which is passed using 'React-router-dom', then the way to access the props is match.params.id

React router: component not updating on url search param change

I have a React application that uses URL search params. Depending on the value of the search parameter v, the component is supposed to show different content.
A possible URL might look like this:
http://localhost:3000/hello?v=12345
Once the v query parameter changes in the URL, I would like my component to update.
http://localhost:3000/hello?v=6789
I'm using using react-router in my App component to display the different components depending on the route. The App component is wrapped into a BrowserRouter component.
render() {
return (
<Switch>
<Route path="/hello" component={Hello}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
If a user clicks on something and the URL search param v changes, I would like the component to display different content. Currently, the URL search param does change, but the component doesn't render again. Any ideas?
As #forJ correctly pointed out, the main idea is to make the component re render once the URL parameters change. I achieved it like so:
render() {
return (
<Switch>
<Route path="/hello" render={() => (<Hello key={this.props.location.key}/>)}></Route>
<Route path="/" component={Home}></Route>
</Switch>
);
}
this.props.location.key changes every time the URL changes (also if the query params change). So if you pass it as props to the component, then the component re renders on URL param changes, even though the base URL (the URL without the URL params) hasn't changed.
I also had to use the withRouter higher order component to make it work.
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Hello));
You have not provided enough information so I cannot be 100% sure where you are going wrong but I can see 1 problem right now in your code. First, you have to let the router know that you will be using parameters. So for example below code
render() {
return (
<Switch>
<Route path="/hello/:yourParam" component={Hello}></Route> // you need the colon here
<Route path="/" component={Home}></Route>
</Switch>
);
}
The above will tell the route that parameter called yourParam will be accessible from the URL
Then it has to be accessed in your component and put into render function somehow (use it to retrieve data from database to render data, just render params directly, etc).
For example below
render(){
<div>
{this.props.match.params.yourParam}
</div>
}
This way, your component will re-render everytime there is param change.
EDIT
Since you want to use query instead of parameter, you would have to retrieve it by going. this.props.match.query.v. You would still have to make sure that variable is rendered depending on query change
If you use useEffect hook make sure to have [props.match.params] instead of empty array []
useEffect(() => {
(async () => {
// Fetch your data
})();
}, [props.match.params]) // <-- By this it will get called when URL changes
of course for class components this.props.match.params

React router redirects - pushState on the same or a different history object?

I'm trying to perform a redirect in a react component using react-router 1.0.2.
In my main app.js I create a hash history:
import createHashHistory from 'history/lib/createHashHistory';
const history = createHashHistory();
...
ReactDOM.render(
<RelayRouter history={history} routes={routes} />,
document.getElementById('root')
);
When I perform a database insert, I want to redirect to another page. I was using this which worked in an earlier version, but doesn't work any more:
import createHashHistory from 'history/lib/createHashHistory';
const history = createHashHistory();
...
// inside Component after a successful DB insert
history.pushState(null, `/#/albums/${newAlbum.id}`, null);
This is in another component (CreateAlbum).
Do I need to call pushState on the same history instance, and if so how can I get hold of it from within a component? If not, what am I doing wrong? The location bar does get updated, but the page stays the same.
Finally, I've manually prefixed /#/ to the URL. This is sloppy and will only work with a Hash History. I saw in the API there's a createHref method, but it expects a pathname which isn't explained. Given that the relevant route is defined as follows, how can I use that to generate a URL in the correct form as well?
<Route
component={App}
queries={ViewerQueries}
path="/">
<Route
path="/create" component={CreateAlbum} />
<Route
path="/albums/:albumId" component={AlbumDetails}
queries={ViewerQueries} />
</Route>
You have to call it on the same history instance.
If you're calling it in the Route components you defined in the component props of Route, such as your App, CreateAlbum or AlbumDetails, the history object is already available as this.props.history, you can console.log(this.props.history) to check it!
So you can simply call:
this.props.history.pushState(null, `/#/albums/${newAlbum.id}`)
to change route.
But if you want to call it in a nested component, you can...
Pass the history as a prop all the way down to your component:
<NestedComponent history={this.props.history} />
Use the history mixin if you're using react-router 1.0.2 (deprecated after 1.0.3)
checkout this SO for more detail
Use the router context's push method if you're using 1.0.4
If your component isn't really deep down, passing the history as prop might be easier!
To expand on Dax's answer,
The Router will pass the history component down to its nodes as a prop - hence this.props.history will be available on all your Route nodes. You shouldn't need to create a new history object for each component.
If you create a non-route component, you will need to pass the history into that component as Dax mentioned or pass some sort of relevant 'event' function from history as passing the entire object might not be totally necessary.
If you're already on 1.0.x - rackt has just released the rc for 2.0.0 as well which should be a fairly smooth upgrade. You can check out the upgrade guide here

Categories

Resources