React-router: Reuse existing rendered component in react-router-dom

Created on 2 Jun 2017  路  7Comments  路  Source: ReactTraining/react-router

We have an expensive to render component that we use as a route in react-router 4.

When navigating away from it and then returning to it, it is recreated and rendered. I would like to cache/hide-unhide it instead of recreating it (avoid mount/unmount and heavy html rendering ).

Most helpful comment

@timdorr This is not just a bug tracker but also a feature request system. This is a feature request. I suggest you read your own template which reads

Have a feature request?
=======================
Remove the template from below and provide thoughtful commentary and code samples on what this feature means for your product. What will it allow you to do that you can't do today? How will it make current work-arounds straightforward? What potential bugs and edge cases does it help to avoid? etc. Please keep it product-centric.

All 7 comments

This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux. Thanks!

@timdorr This is not just a bug tracker but also a feature request system. This is a feature request. I suggest you read your own template which reads

Have a feature request?
=======================
Remove the template from below and provide thoughtful commentary and code samples on what this feature means for your product. What will it allow you to do that you can't do today? How will it make current work-arounds straightforward? What potential bugs and edge cases does it help to avoid? etc. Please keep it product-centric.

This isn't a concern of React Router, though. You need to know how to keep a copy of a component around after it disappears from the tree, and how to restore it when it comes back into the tree. Those are more generic concepts that live above the Router.

This isn't something we're going to implement here because it's better done as an external library.

I get that this isn't a bug, and not even something React-Router should implement in code. But it sure is relevant to document good patterns.

This subject seems to come up often, so if you could point us in the right direction, I'd be happy to submit a PR to the documentation afterwards.

@andrioid

import warning from 'warning';
import invariant from 'invariant';
import React from 'react';
import PropTypes from 'prop-types';
import matchPath from 'react-router/matchPath';

const isEmptyChildren = children => React.Children.count(children) === 0;

/**
 * The public API for matching a single path and rendering.
 */

window.CACHE_PAGES = {};

class Route extends React.Component {
  static propTypes = {
    computedMatch: PropTypes.object, // private, from <Switch>
    path: PropTypes.string,
    exact: PropTypes.bool,
    strict: PropTypes.bool,
    sensitive: PropTypes.bool,
    component: PropTypes.func,
    render: PropTypes.func,
    children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
    location: PropTypes.object
  };

  static contextTypes = {
    router: PropTypes.shape({
      history: PropTypes.object.isRequired,
      route: PropTypes.object.isRequired,
      staticContext: PropTypes.object
    })
  };

  static childContextTypes = {
    router: PropTypes.object.isRequired
  };

  getChildContext() {
    return {
      router: {
        ...this.context.router,
        route: {
          location: this.props.location || this.context.router.route.location,
          match: this.state.match
        }
      }
    };
  }

  state = {
    match: this.computeMatch(this.props, this.context.router)
  };

  computeMatch(
    { computedMatch, location, path, strict, exact, sensitive },
    router
  ) {
    if (computedMatch) return computedMatch; // <Switch> already computed the match for us

    invariant(
      router,
      'You should not use <Route> or withRouter() outside a <Router>'
    );

    const { route } = router;
    const pathname = (location || route.location).pathname;

    return path
      ? matchPath(pathname, { path, strict, exact, sensitive })
      : route.match;
  }

  componentWillMount() {
    warning(
      !(this.props.component && this.props.render),
      'You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored'
    );

    warning(
      !(
        this.props.component &&
        this.props.children &&
        !isEmptyChildren(this.props.children)
      ),
      'You should not use <Route component> and <Route children> in the same route; <Route children> will be ignored'
    );

    warning(
      !(
        this.props.render &&
        this.props.children &&
        !isEmptyChildren(this.props.children)
      ),
      'You should not use <Route render> and <Route children> in the same route; <Route children> will be ignored'
    );
  }

  componentWillReceiveProps(nextProps, nextContext) {
    warning(
      !(nextProps.location && !this.props.location),
      '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.'
    );

    warning(
      !(!nextProps.location && this.props.location),
      '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.'
    );

    this.setState({
      match: this.computeMatch(nextProps, nextContext.router)
    });
  }

  render() {
    const { match } = this.state;
    const { children, component, render, isCache } = this.props;
    const { history, route, staticContext } = this.context.router;
    const location = this.props.location || route.location;
    const props = { match, location, history, staticContext };

    if (component)
      if (match) {
        window.PATHNAME = location.pathname;
        if (window.CACHE_PAGES[location.pathname]) {
          return null;
        }
        window.CACHE_PAGES[location.pathname] = React.createElement(
          component,
          props
        );
        return null;
      } else {
        return null;
      }

    if (render) return match ? render(props) : null;

    if (typeof children === 'function') return children(props);

    if (children && !isEmptyChildren(children))
      return React.Children.only(children);

    return null;
  }
}

export default Route;

@jsu93 I think it just do caching of elements, but doesn't solve the mount/unmount issues. It's good enough for text content, but for pages with a lot of images, it still suffers.

@ssssssssssss

  • You can map window.CACHE_PAGES in another container;
  • With window.PATHNAME, you can switch to current component with style="display:none" and style="display:block"
Was this page helpful?
0 / 5 - 0 ratings

Related issues

yormi picture yormi  路  3Comments

davetgreen picture davetgreen  路  3Comments

winkler1 picture winkler1  路  3Comments

stnwk picture stnwk  路  3Comments

ryansobol picture ryansobol  路  3Comments