React reload component on dynamic route - javascript

<Route path="/user/:username/" component={UserProfile} />
I have a route set up like above and having issue on
<Link to={"/user/" + userName + "/"}>user profile</Link>
When user goes to /user/user1/ to /user/user2/ because it does not reload the component but just update the states.
What is the best way to solve this issue? I need HTTP request in componentDidMount to be executes when username changes.

Routed component doesn't reload when route parameters change. But it will call componentWillReceiveProps() as the component get different props. So call HTTP request inside both componentWillReceiveProps() and componentDidMount as React doesn't call componentWillReceiveProps() with initial props during mounting.

Related

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,
});
}
}

Prevent React Route to re-run componentDidMount() on traversing back on same route

For a given route localhost:3000/my-link, component MyLink is rendered.
In MyLink component, ajax request is fired in componentDidMount() which then sets the state object of the MyLink class and renders the component.
But when navigating to some other route and switching back to /my-link ajax is fired again. In short, whole state previously populated is lost. Can there be a way to check if the previous state is already populated, then prevent componentDidMount to be called?
componentDidMount() is invoked immediately everytime a component is mounted in the DOM. What you're trying to do can be achieved by integrating react redux store. Call the Ajax call according to store state variable. On firing the AJAX requests Dispatch the action to make the variable true.
componentDidMount() {
if(!this.props.variable)
{
//AJAX CALL
dispatch({ type: 'SET_VARIABLE', value: true });
}
}
Once it sets to true, it won't be called next time you navigate to this route.
When you switch from your existing route localhost:3000/my-link to any other page like localhost:3000/other-link, Your MyLink component got unmounted but at the same time, your data fetched in MyLink component is still in your redux store.
Here's what you can do;
If the data which you are fetching in MyLink is frequently changing then it is a good idea to fetch it again when you visit the same link again. Means, You need to clear the state when your component unmounts.
componentWillUnmount() {
// resetData is an action which will clear the data in your MyLink reducer.
this.resetData();
}
So, When you visit the same link again, The component won't have any previous data and it will fetch the fresh data.
If that data is not frequently changing and you're okay with getting it just one time then you can make a conditional call to your action.
componentDidMount() {
// this.props.data is a state from redux store.
if(!this.props.data){
this.getData();
}
}

How to dispatch redux actions using react-router v4?

I'm trying to combine react-router v4, react, and redux. Because react-router tracks the URL, I've opted to keep that piece of state out of the redux-model.
But i still need a way to to dispatch a redux action when a route change happens by react-router. Where is the best place to do that?
My first attempt was to put it in the onClick attribute of react-router's Link:
render() {
// link config
const links = this.props.photo.album( album => {
<Link key={album.name}
to=`/album/${album.name}`
onClick={() => this.props.dispatchAction(album.name)} />
})
// route config
return (
<div>
{links}
<Route path={`/album/:albumName`} component={Album}/>
</div>
)
}
The idea is that, when a user clicks a link, the dispatchAction() will update the redux state and then the Album component gets loaded.
The problem is that if a user navigates to the URL directly (eg /album/a1), the action is never dispatched, since the link is technically never clicked.
Because of this I removed the onClick portion of the Link, and moved the dispatchAction to the lifecycle methods of the Album component:
class Album extends Component {
// invoked when user navigates to /album/:albumName directly
componentDidMount() {
this.props.dispatchAction(this.props.match.params.albumName)
}
// invoked whenever the route changes after component mounted
componentWillReceiveProps(nextProps) {
if (this.props.match.params.albumName != nextProps.match.params.albumName) {
this.props.dispatchAction(nextProps.match.params.albumName)
}
....
}
Now whenever the Album component is mounted or its properties changed, it will dispatch the redux-action. Is this the correct approach for combining these libraries?
react-router-redux does this for you by applying a middleware on your store that dispatches actions on route changes, also on the initial route change. It's definitely the cleanest approach.
The only downside is it's still alpha but we have been using it without any issues for a while. It is also part of the react-router additional packages repo for a while.
You could create a custom Route component that dispatches your action in componentDidMount:
class ActionRoute extends React.Component {
componentDidMount() {
const { pathname } = new URL(window.location.href);
const [albumName] = pathname.split('/').slice(-1);
this.props.dispatchAction(albumName);
}
render() {
return <Route {...this.props} />
}
}
This will now dispatch your action whenever the route is loaded. Composability FTW!.
Side note: if you use ActionRoute for parameterized routes, (eg /album/1 and /album/2), you'll need to also dispatch the action in componentDidUpdate as the component isn't unmounted / remounted if you navigate from /album/1 to /album/2.

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 changing params doesn't fire componentWillRecieveProps

The Problem: Changing the parameters of a <Route /> component does not update the component it is rendering. The route change is shown in the URL bar, but directly rendering {this.props.match.params.id} shows the old :id and not the new one reflected in the URL bar.
Update: I fixed this by moving the <BrowserRouter /> out from the index.js file and into the App.js file. It is no longer the direct child of Provider and is instead the child of the App component. No clue why this makes everything suddenly work.
What I am doing: I have a <Link to="/user/11" /> that goes from user/7 (or any current ID) to a /user/11
The componentWillReceiveProps(newProps) of the component it is rendering is not fired.(This component is connected using react-redux if that helps any. I tried applying withRouter around the connection and that did not help)
If I manually refresh the page in chrome (using CTRL-R or the refresh button) the page shows the new data, rendering the "new" param.
TLDR: Switching from /user/7 to /user/11 does not fire that componentWillRecieveProps function and therefore leaving the component displaying the old state
Question: What am I doing incorrectly here that causes componentWillReceiveProps to not fire.
I am using react-router v4 and the latest create-react-app
This is my CWRP function:
componentWillReceiveProps(newProps) {
console.log("getProps")
this.props.getUser(newProps.match.params.id)
if (newProps.match.params.id == newProps.currentUser.id) {
this.setState({ user: "currentUser" })
} else {
this.setState({ user: "selectedUser" })
}
}
This is the full code of my component: https://gist.github.com/Connorelsea/c5c14e7c54994292bef2852475fc6b43
I was following the solution here and it did not seem to work for me. Component does not remount when route parameters change
You'll need to use React Router Redux

Categories

Resources