onEnter prop in react-router v4 - javascript

I'm using react-router v4. I find it confusing that when I click on <Link> component, the route is immediately changed. I want to stay at the current route before I fetch the data, not to switch the route and then fetch the data. I find that in react-router v3 there's a onEnter hook, but now in react-router v4, it is deprecated. So how can I fetch data right after I click on <Link> component and before the route is changed?
Example:
it's: at A -> click at <Link> to B -> fetching data for B -> render B
not: at A -> click at <Link> to B -> render Route B -> fetching data for B -> rerender B

In react-router v4, you can make use of render prop to Route to replace the onEnter functionality existing in react-router v3
Suppose you have a route like in react-router v3
<Route path="/" component={Home} onEnter={getData} />
The Equivalent of this in react-router v4 would be
<Route exact path="/" render= {() => {getData(); return <Home data={this.state.data}/>}} />
However the suggested method is to make use of lifecycle methods in the component.
From the Github discussion:
We have no lifecycle hooks that receive props on initial render.
We no longer have a "route lifecycle". Instead, since <Match>
components are real components (not pseudo-components like s
were) they get to participate in React's lifecycle, which means they
can just use componentWillMount.
So one of the suggested solution is:
v4 is all about routes just being components. That means taking advantage of
lifecycle methods.
componentWillMount() { // or componentDidMount
fetch(this.props.match.params.id)
}
componentWillReceiveProps(nextProps) { // or componentDidUpdate
if (nextProps.match.params.id !== this.props.match.params.id) {
fetch(nextProps.match.params.id)
}
}

Related

React router v6 access route params and pass as props

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

Re-render a React Component

I have a profile component with URL: '/profile/id'.
Now, suppose I have two users having IDs: id_A and id_B.
I am at URL: '/profile/id_A' and I have a button which sends me to URL: '/profile/id_B' using:
<Link to={{ pathname: `/profile/id_B` }}></Link>
Now, my component won't re-render because there is no state change for which I cannot retrieve my user-data from the server since all my requests are done on componentDidMount.
So, how do I re-render my component for the above scenario?
UPD 1:
This is my Route code:
<Route path="/profile/:userId" component={Profile} />
UPD 2:
I have 3 components nested:
Profile -> FollowList -> Card
I have my link in my card component and this is the code for routing it to 'profile/id_B'.
<Link to={{ pathname: `/profile/${this.props.user.userId}` }}></Link>
Can this be a problem that the below answers are not working?
Ciao, to re-render a component you can use shouldComponentUpdate(nextProps) function. Basically React triggers this function to ask you if component should be updated (re-rendered) and here you can write the logic you need to re-render the component. Take a look at this guide.
It appears you need to use the Route component, placing your "pages" on them with one of the designed methods. You can read more about it here:
https://reactrouter.com/web/api/Route
The recommended way of doing it is using the children of the Route, which is supposed to mount the page when the url matches, and unmount when they don't. So something like:
<Route path="/to">
<My Page/>
</Route>
The thing is that your component is getting rerendered but as you have your code in componentDidMount and it is called only once when the component is mounted so you are facing the difficulty.
So to fix this problem you need to unmount and mount your component again, this can be done using the inline function inside component prop.
According to react-router-docs
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.
So you need to refactor your code and use
<Route path="/profile/:userId" component={() => (<Profile />)} />
This will mount and unmount on component on each render and you can access the component did mount.
Alternate Solution
You can also use ComponentDidUpdate and check on each update if the id is same or not, if id is not same fetch the data.
componentDidUpdate(prevProps) {
if (prevProps.match.params.userId !== this.props.match.params.userId) {
//call your fetch function here and then set the data in state
setState({
userId: data,
});
}
}

React Router 4, Route with stateless component

If I want to route to a stateless component I use the component which will give me a match params
e.g.
<Route path="/ingredient/:ingredientID" component={IngredientPage} />
the problem with using the "component" attribute is I can't pass in a class function to be used. I'd like to fetch api data based on the id before I load the component and I want the component to be a function component.
if I use render I can't call a state change because that will re-render (infinite loop).
<Route path="/ingredient/:ingredientID" render={(props) => {
this.loadIngredient(props.match.params.ingredientID)
//this will change state which creates a loop
return <IngredientPage ingredient={this.state.ingredient} />
} />
So far the only way I can figure this out is making the component a state component like it's parent but then I am dealing with two states.
Any suggestions?

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

Passing state to react-router 1.x

In react-router version 0.13.3 I have
Router.run(routes, createBrowserHistory, function(Handler, state) {
React.render(<Handler query={ state } path={ state.pathname }/>, document.getElementById('app'))
});
In react-router 1.0.0.rc3 I have
let history = createBrowserHistory();
React.render(<Router history={ history } >{ routes }</Router>, document.getElementById('app'));
With react-router 0.13.3, I was able to pass state to my Components with a callback. I was wondering how I can do the same with version react-router 1.x ?
React Router 1.0 now creates elements from your route components, passing them the following routing-related state as props by default:
history - a History object
location - a Location object
params - parameters extracted from the URL
route - the route object the rendered component belongs to
routeParams - parameters extracted from the URL for the route object the rendered component belongs to
routes - an array of all the route objects which were matched
You should be able to use these to get a hold of routing-related state, e.g. to get the pathname, you could use this.props.location.pathname inside a route component, or pass these onto children which need them.
The 1.0 upgrade guide has more details about this.
If you want to pass additional props to an individual route component when rendering it, you can use React.cloneElement() like so:
{React.cloneElement(this.props.children, {extra: 'extra prop'})
If you want to pass additional props when a route component element is being created, you can pass a custom createElement() function to <Router>.

Categories

Resources