Component does not remount when route parameters change

ReactjsReact Router

Reactjs Problem Overview


I'm working on a react application using react-router. I have a project page that has a url as follows:

myapplication.com/project/unique-project-id

When the project component loads, I trigger a data request for that project from the componentDidMount event. I'm now running into an issue where if I switch directly between two projects so only the id changes like this...

myapplication.com/project/982378632
myapplication.com/project/782387223
myapplication.com/project/198731289

componentDidMount is not triggered again so the data is not refreshed. Is there another lifecycle event I should be using to trigger my data request or a different strategy to tackle this issue?

Reactjs Solutions


Solution 1 - Reactjs

If you do need a component remount when route changes, you can pass a unique key to your component's key attribute (the key is associated with your path/route). So every time the route changes, the key will also change which triggers React component to unmount/remount. I got the idea from this answer

Solution 2 - Reactjs

Here is my answer, similar to some of the above but with code.

<Route path="/page/:pageid" render={(props) => (
  <Page key={props.match.params.pageid} {...props} />)
} />

Solution 3 - Reactjs

If the link is directing to the same route with just a different param, it's not remounting, but instead receiving new props. So, you could use the componentWillReceiveProps(newProps) function and look for newProps.params.projectId.

If you're trying to load data, I would recommend fetching the data on before the router handles the match using static methods on the component. Check out this example. React Router Mega Demo. That way, the component would load the data and automatically update when the route params change without needing to rely on componentWillReceiveProps.

Solution 4 - Reactjs

You have to be clear, that a route change will not cause a page refresh, you have to handle it yourself.

import theThingsYouNeed from './whereYouFindThem'

export default class Project extends React.Component {

    componentWillMount() {

        this.state = {
            id: this.props.router.params.id
        }

        // fire action to update redux project store
        this.props.dispatch(fetchProject(this.props.router.params.id))
    }

    componentDidUpdate(prevProps, prevState) {
         /**
         * this is the initial render
         * without a previous prop change
         */
        if(prevProps == undefined) {
            return false
        }
    
        /**
         * new Project in town ?
         */
        if (this.state.id != this.props.router.params.id) {
           this.props.dispatch(fetchProject(this.props.router.params.id))
           this.setState({id: this.props.router.params.id})
        }
    
    }

    render() { <Project .../> }
}

Solution 5 - Reactjs

If you have:

<Route
   render={(props) => <Component {...props} />}
   path="/project/:projectId/"
/>

In React 16.8 and above, using hooks, you can do:

import React, { useEffect } from "react";
const Component = (props) => {
  useEffect(() => {
    props.fetchResource();
  }, [props.match.params.projectId]);

  return (<div>Layout</div>);
}
export default Component;

In there, you are only triggering a new fetchResource call whenever props.match.params.id changes.

Solution 6 - Reactjs

Based on answers by @wei, @Breakpoint25 and @PaulusLimma I made this replacement component for the <Route>. This will remount the page when the URL changes, forcing all the components in the page to be created and mounted again, not just re-rendered. All componentDidMount() and all other startup hooks are executed also on the URL change.

The idea is to change components key property when the URL changes and this forces React to re-mount the component.

You can use it as a drop-in replacement for <Route>, for example like this:

<Router>
  <Switch>
    <RemountingRoute path="/item/:id" exact={true} component={ItemPage} />
    <RemountingRoute path="/stuff/:id" exact={true} component={StuffPage} />
  </Switch>
</Router>

The <RemountingRoute> component is defined like this:

export const RemountingRoute = (props) => {
  const {component, ...other} = props
  const Component = component
  return (
    <Route {...other} render={p => <Component key={p.location.pathname + p.location.search}
                                              history={p.history}
                                              location={p.location}
                                              match={p.match} />}
    />)
}

RemountingRoute.propsType = {
  component: PropTypes.object.isRequired
}

This has been tested with React-Router 4.3.

Solution 7 - Reactjs

[@wei's answer][1] works great, but in some situations I find it better to not set a key of inner component, but route itself. Also, if path to component is static, but you just want component to remount each time user navigates to it (perhaps to make an api-call at componentDidMount()), it is handy to set location.pathname to key of route. With it route and all it's content gets remounted when location changes.

const MainContent = ({location}) => (
    <Switch>
        <Route exact path='/projects' component={Tasks} key={location.pathname}/>
        <Route exact path='/tasks' component={Projects} key={location.pathname}/>
    </Switch>
);

export default withRouter(MainContent)

[1]: https://stackoverflow.com/a/39150493/4525236 "@wei's answer"

Solution 8 - Reactjs

This is how I solved the problem:

This method gets the individual item from the API:

loadConstruction( id ) {
    axios.get('/construction/' + id)
      .then( construction => {
        this.setState({ construction: construction.data })
      })
      .catch( error => {
        console.log('error: ', error);
      })
  }

I call this method from componentDidMount, this method will be called just once, when I load this route for the first time:

componentDidMount() {   
    const id = this.props.match.params.id;
    this.loadConstruction( id )
  }

And from componentWillReceiveProps which will be called since the second time we load same route but different ID, and I call the first method to reload the state and then component will load the new item.

componentWillReceiveProps(nextProps) {
    if (nextProps.match.params.id !== this.props.match.params.id) {
      const id = nextProps.match.params.id
      this.loadConstruction( id );
    }
  }

Solution 9 - Reactjs

You can use the method provided:

useEffect(() => {
  // fetch something
}, [props.match.params.id])

when component is guaranteed to re-render after route change then you can pass props as dependency

Not the best way but good enough to handle what you are looking for according to Kent C Dodds: Consider more what you want to have happened.

Solution 10 - Reactjs

React Router v6+

You need to specify a key. In earlier versions of React Router you could do the following:

<Route path="/project/:pid"
       render={(props) => (
           <Page key={props.match.params.pid} {...props} />)}
/>

Since render can't be used with <Route> anymore (starting from React Router v6), one of the simplest solutions is to create an auxiliary component:

const PageProxy = (props) =>
{
  const { pid } = useParams();
  return <Page key={pid} {...props} />;
}

And your <Route> becomes simply:

<Route path="/project/:pid" element={<PageProxy />} />

Solution 11 - Reactjs

If you are using Class Component, you can use componentDidUpdate

componentDidMount() {
    const { projectId } = this.props.match.params
    this.GetProject(id); // Get the project when Component gets Mounted
 }

componentDidUpdate(prevProps, prevState) {
    const { projectId } = this.props.match.params

    if (prevState.projetct) { //Ensuring This is not the first call to the server
      if(projectId !== prevProps.match.params.projectId ) {
        this.GetProject(projectId); // Get the new Project when project id Change on Url
      }
    }
 }

Solution 12 - Reactjs

Here is a pretty simple solution: do a location check in componentDidUpdate, and have a getData function that has the data fetching part with setState:

componentDidUpdate (prevProps) {
    if (prevProps.location.key !== this.props.location.key) {
        this.getData();
    }
}


getData = () => {
    CallSomeAsyncronousServiceToFetchData
        .then(
            response => {
                this.setState({whatever: response.data})
            }
        )
}

Solution 13 - Reactjs

I will try to give the updated answer.

So basically what you want is the Component to get remounted whenever we find a change in params.

Now react-router>6.0 has been released and this answer will be for React Router ^V6

So suppose I have a path

/user/<userId>

Here <userId> will be replaced by the actual userId

So the initialization of this path will be like this

<BrowserRouter>
    <Routes>
        <Route
           path="/user/:userid"
              element={<YourComponent/>}
         />
     </Routes>
</BrowserRouter>

Now in <YourComponent/> receiving end

import { useParams } from "react-router-dom";
import { useEffect } from "react";

export default function YourComponent(){
    const {userid} = useParams();

    useEffect(() => {
        // Some Logic Here
    },[userid])

   return (<Component/>)
}

So this useEffect() will mount itself everytime there is a change in the params (userid in this case)

Thus the outcome is whenever you hit the URL with same params (It is not equal to reloading the page. I mean to navigate to the same URL) the component will not remount itself but there will be remounting whenever there is a change in the params which I think solves your problem.

Solution 14 - Reactjs

react-router is broken because it must remount components on any location change.

I managed to find a fix for this bug though:

https://github.com/ReactTraining/react-router/issues/1982#issuecomment-275346314

In short (see the link above for full info)

<Router createElement={ (component, props) =>
{
  const { location } = props
  const key = `${location.pathname}${location.search}`
  props = { ...props, key }
  return React.createElement(component, props)
} }/>

This will make it remount on any URL change

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionConstellatesView Question on Stackoverflow
Solution 1 - ReactjsweiView Answer on Stackoverflow
Solution 2 - ReactjsBreakpoint25View Answer on Stackoverflow
Solution 3 - ReactjsBradByteView Answer on Stackoverflow
Solution 4 - ReactjschickenchilliView Answer on Stackoverflow
Solution 5 - ReactjsAlfonso Embid-DesmetView Answer on Stackoverflow
Solution 6 - ReactjsJuha SyrjäläView Answer on Stackoverflow
Solution 7 - ReactjsPaulus LimmaView Answer on Stackoverflow
Solution 8 - ReactjsOscar JovannyView Answer on Stackoverflow
Solution 9 - ReactjsVinceView Answer on Stackoverflow
Solution 10 - ReactjsAleksey SlootskyView Answer on Stackoverflow
Solution 11 - ReactjsAngel MasView Answer on Stackoverflow
Solution 12 - ReactjsR GView Answer on Stackoverflow
Solution 13 - ReactjsShubhrajyoti DeyView Answer on Stackoverflow
Solution 14 - ReactjscatamphetamineView Answer on Stackoverflow