19

I have a component at a given route, say app.com/cars/1

I have a sidebar with links to different cars, eg /cars/2, /cars/3 etc.

The issue I'm having is when you change links, say go from cars/1 to cars/2, the component doesn't unmount, and I get componentWillReceiveProps fired. If I go to another page with a different component, say /trucks, the component is unmounted and all is well.

How can I unmount my component when the route changes? I have all kinds of state and flux stuff that I want cleared for this next car. Or if not unmount, is there a typical way people handle this kind of issue? I can't imagine this isn't very common.

(note I'm using react-router)

Dorklord
  • 416
  • 2
  • 6
  • 17
Ben
  • 5,283
  • 9
  • 35
  • 44

3 Answers3

17

I think the normal way to handle this is just to unregister and reregister your listeners, reset your state, and so on in componentWillReceiveProps. It's normal to create abstractions around this behavior:

componentWillMount: function() {
  this.setupStuff(this.props);
}

componentWillUnmount: function() {
  this.tearDownStuff();
}

componentWillReceiveProps: function(nextProps) {
  this.tearDownStuff();
  this.setupStuff(nextProps);
}

setupStuff: function(props) {
  this.setState(this.getDataFromStore(props.store));
  props.store.listen(this.handler); // or whatever
}

tearDownStuff: function(props) {
  props.store.unlisten(this.handler); // or whatever
}

However, if you really wanted to remount your components, there are a couple options you can use.

If you don't want any of your components to remain mounted across route changes, you can utilize the createElement option of the router to add a unique key to the components:

function createElement(Component, props) {
  var key = ...; // some key that changes across route changes
  return <Component key={key} {...props} />;
}

// ...

<Router createElement={createElement}>
  ...

However, I don't recommend this. Not only does it make your app slower because every route component is remounting each time, but it also completely disables things like animations between subsequent renders of the same route handler with different props.

If you only want a certain route to always rerender, you can give it a key in the parent via React.cloneElement:

render: function() {
  var key = ...; // some key that changes across route changes
  return React.cloneElement(
    React.Children.only(this.props.children),
    {key: key}
  );
}
Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
  • 1
    Still relevant now, However, its not easy to reset state if your state is coming from a hook you don't control. – cdosborn Jul 02 '20 at 00:10
  • @cdosborn Yeah. I think I would probably wrap the hook usage in a sub-component, and render it with a changing `key` prop as necessary based on some other data from a wrapping component to enable a forced remount. See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key – Michelle Tilley Jul 02 '20 at 05:05
3

I ended up just doing:

const createElement = (Component, props) =>
  <Component key={props.params.id} {...props}/>;

ReactDOM.render(
  <Router history={browserHistory} createElement={createElement}>
    <Route path="courses/:id" component={Page_courses_id}/>
  </Router>
);

and ignoring potential performance issues (if they ever occur), in my mind maintenance cost (of resetting state of all components under :dynamic-segment, of refetching data in componentWillReceiveProps, etc.) is not worth it.

Evgenia Karunus
  • 10,715
  • 5
  • 56
  • 70
  • I have somewhat similar case...a sidebar which can open and close on left and on right i have dhtmlx-scheduler component.On page reload everything works fine, but when i go to Route3 and come back to Route2 on which sidebar and dhtmlx-cheduler are present, then on opening the sidebar, the scheduler component should adjust its width according to the width left when sidebar is expanded, but some portion of scheduler is cut off. On page hard refresh, although it works fine, and adjust its width perfectly when sidebar is expanded. Should i unmount scheduler component everytime route changes. – sk215 Oct 26 '20 at 23:05
  • Please find the github link attached https://github.com/sonalk215/dhtml Any pointers will be really elpful – sk215 Oct 27 '20 at 00:50
  • @sk215, I'm sure you'll have more luck submitting this as a question. – Evgenia Karunus Oct 27 '20 at 16:15
  • I raised a new question, thanks for the suggestion lakesare Question link https://stackoverflow.com/questions/64563819/react-with-dhtmlx-scheduler – sk215 Oct 27 '20 at 22:31
1

I chose the Michelle Tilley option with change key, but I did not set the key for the entire component in the router, but only for the one where I needed to update with restarting the hooks.

This works great.

const UniqComponent = () => {
  const uniqId = 123; // may be carId
  return <div key={uniqId} />
}

ReactDOM.render(
  <Router history={browserHistory}>
    <Route path="cars/:id" component={UniqComponent}/>
  </Router>
);