Keep components between routes using react-router

2019-02-14 02:39发布

问题:

I have a multi-steps form and I'm using react-router to navigate between the different steps. In some of the steps I show an iframe to the user. When the user navigates between steps it always unmount and re-mount the iframe, this causes two problems:

  1. It reloads the iframe from its source, which makes it jump.
  2. Since it's an iframe I can't control its internal state and it loses the state between steps. So if the user had some inputs to the iframe, when moving to the next step the inputs are lost.

Is there any way to keep the iframe instance in some global store and only mount it to the DOM when necessary ?

Any other ideas how to solve this problem ?

Thanks.

回答1:

Is there any way to keep the iframe instance in some global store and only mount it to the DOM when necessary ?

Yes, you could, but if you even remove an iframe from the DOM and re-append it later it still gets reloaded, so in that way the problem actually has very little to do with React's component tree. What you really need is to just hide your iframe and show it again later.

You could of course hide and show the iframe in React like this:

{ <iframe src="..." style={{display: this.state.showing ? "block" : "none"}} /> }

In this case you need to render the iframe at some place that does not get unmounted. You can use components further down in your tree to communicate back upwards to show/hide the iframe.


But if you really want to be able to hide/show the iframe from different places in your component tree that get mounted and unmounted, you can, but it gets quite a bit trickier and not a typical use-case of React.

You'll need to create the DOM iframe yourself and append it somewhere into the DOM that is outside React's component tree (this is generally an anti-pattern in React). Then you can use a proxy component to show/hide this DOM element when mounted and unmounted.

Here's an example that appends an iframe to the document.body and shows it when a component is mounted, and hides it when the component is unmounted:

class WebView extends React.Component {
  static views = {};
  componentDidMount() {
    if (!WebView.views[this.props.url]) {
      WebView.views[this.props.url] = this.createWebView(this.props.url);
    }
    WebView.views[this.props.url].style.display = "block";
  }
  componentWillUnmount() {
    WebView.views[this.props.url].style.display = "none";
  }
  createWebView(url) {
    let view = document.createElement("iframe");
    view.src = this.props.url;
    document.body.appendChild(view);
    return view;
  }
  render() {
    return null;
  }
}

Here it is working on CodePen: notice that when you hide (unmount) then show (mount) the WebView the iframe state (for example search input) stays the same.

You will also need to position and size the iframe to appear within your layout correctly. I haven't shown this because it's a bit difficult to solve generally.

Note that this solution is similar to the "portal" pattern. The difference here is to not ever unmount the iframe in order to preserve its state and prevent reloading.



回答2:

Quite simply, there's no way to preserve a component in the render tree if the parent is not rendered. That is, when the route changes, your component will necessarily be unmounted if it's a child to a Route.

One way to side-step that is to make your component not a child to any Route at all. This way, you would always render this component alongside every Route. Then you can simply use styles to show/hide it when appropriate. Obviously, this will partly compromise the structure of your DOM.