25

I am using react-router and react-redux. I have two routes like this:

<Route path='/edit'     component={ EditNew } />
<Route path='/edit/:id' component={ EditDraft } />

where EditNew and EditDraft are data-providing containers that wrap an Editor component using the react-redux connect function:

const EditNew = connect(state => ({}))(React.createClass({
    render() {
        return <Editor />;
    }
}));

and

const EditDraft = connect(state => ({ drafts: state.drafts }))(React.createClass({
    render() {
        const { params, drafts } = this.props;
        const draft = findDraft(params.id, drafts);
        return <Editor draft={ draft } />;
    }
}));

Now, Editor is rigged up in such a way that when you begin typing into a blank Editor, it triggers a history.replaceState() from /edit to /edit/:id with a ranomly generated ID. When this happens, I get the following sequence of events:

  • EditorNew unmounts
  • Editor unmounts
  • EditorDraft renders and mounts
  • Editor renders and mounts

When I coded these two containers, I thought that the Editor component contained in both of them would be reconciled without unmounting and remounting. This is problematic for me for several reasons besides the extra unnecessary work, chief among which are that the editor ends up losing focus and proper cursor range after the unmount and remount.

To no avail I have tried specifying key for the Editor component to hint to the reconciliation system that it's the same component, and I've tried shouldComponentUpdate, but that doesn't get called, which makes sense given what React is doing.

Apart from combining the two containers into one container with more complicated render() logic, is there anything I can do to prevent the Editor component from unmounting/remounting during the history transition?

Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160
  • It sounds like it's working the way it's supposed to; you are changing the top-level component, of which `Editor` is a child, so it should be completely destroyed and recreated under the new parent. – Evan Davis Oct 15 '15 at 14:54
  • So I should merge the containers eh? I am looking at https://github.com/rackt/redux-router but I don't want to add more stuff to this project considering I want to migrate it to Relay soon. – Dmitry Minkovsky Oct 15 '15 at 14:55
  • Yeah, I would advise a single container, especially since you are talking about different states of the same thing, not using the `Editor` in a wholly different capacity. – Evan Davis Oct 15 '15 at 14:59
  • Thanks, will do that. – Dmitry Minkovsky Oct 15 '15 at 15:03

3 Answers3

6

React’s Reconciliation Algorithm says that if the element has a different type (in this case, EditNew and EditDraft), then React will “tear down the old tree and build the new tree from scratch.”

To prevent this, you need to use the same component for both routes.

Thai
  • 10,746
  • 2
  • 45
  • 57
1

You can use shouldComponentUpdate and, if the route has changed from /edit to /edit/:id (you can check this getting the router info from the state connected to your component) return false, so it won't refresh the component.

Franco Risso
  • 1,572
  • 2
  • 13
  • 18
0

Chances are that this isn't possible with react-router <= v3.

With react-router v4, this should be possible now: https://github.com/ReactTraining/react-router/issues/4578

amann
  • 5,449
  • 4
  • 38
  • 46