Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep components between routes using react-router

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.

like image 956
E. Maimon Avatar asked May 18 '16 13:05

E. Maimon


People also ask

How do I share data between two routes in react?

You can use the Link component from react-router and specify to={} as an object where you specify pathname as the route to go to. Then add a variable e.g. data to hold the value you want to pass on.

How do you secure routes in react router?

To protect routes, the private components must also have access to the isLoggedIn value. You can do this by creating a new component that accepts the isLoggedIn state as a prop and the private component as a child. For instance, if your new component is named "Protected", you would render a private component like this.

What is the BrowserRouter /> component?

BrowserRouter: BrowserRouter is a router implementation that uses the HTML5 history API(pushState, replaceState and the popstate event) to keep your UI in sync with the URL. It is the parent component that is used to store all of the other components.

How is store connected to react Routes?

Connected React Router is a Redux binding for React Router v4 and v5. It synchronizes router state with Redux store via a unidirectional flow and uses react-hot-loader to facilitate hot reloading of functional components while preserving state.


2 Answers

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.

like image 135
Aaron Beall Avatar answered Nov 04 '22 08:11

Aaron Beall


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.

like image 43
Guilherme Rodrigues Avatar answered Nov 04 '22 08:11

Guilherme Rodrigues