Git Product home page Git Product logo

react-live-route's Introduction

react-live-route

An enhanced version of react-router-v4 Route Component that keeps route component alive on unmatched path and restore it completely on match path.

NPM Version Build Status Coverage Status

Document

中文

Feedback

Feel free to open an issue to ask for help or have a discussion. If there is a detailed code problem help is needed, please fork this minimal demo to provide a reproduce scenario, or your issue might be closed directly due to lack of information.

Demo

codeSandbox

You can experience and review the source code on codeSandbox.

Edit react-live-route-demo-1

QR code

You also can scan the QR code of the demo above to experience it on mobile device.

qr

Install

npm install react-live-route

or

yarn add react-live-route

About

It will hide component of route instead of unmout it when the path is not match and restore it when come back. There are a few APIs provided to control the hidden condition of component.

Example:

There is a item list page, click on the items on this page will enter the item detail page. When entering the detail page, item list page will be hidden and it will keep hidden when you are on item detail page. Once back to the list page, the list page will be restored to the last state of leaving.

Features

  • ✅ Fully compatible with react-router-v4, all passed the react-router-v4 unit tests.
  • 🎯 Completely restored to the state of the last time you left (scroll position included).
  • 🔒 Minimally invasive, all you need to do is import LiveRoute.
  • ✌️ Super easy API.

Hint

  • Using with , see [Use in <Switch>](#Use in <Switch>) for detail.
  • Using with code splitting, see ensureDidMount for detail.
  • If LiveRoute's parent route is unmounted on current location, then it will also be unmounted . This is determined by the top-down design principle of React. You can use LiveRoute to declares a parent route to solve this problem or stop nestting the router.
  • In some cases the DOM of LiveRoute will be modified directly and the scroll position will not change when navigation. This is not a problem with react-live-route. You can scroll the screen to the top manually and you may get some help from this article from react-router. By the way, if the scroll position will be restored by LiveRoute, it will come up after the scroll operation in componet of LiveRoute due to the render order of React.

Usage

1. Enhance Route with withRouter

The class imported from react-live-route must be wrapped by withRouter to touch the property of context to work as expected.

import NotLiveRoute from 'react-live-route'
import { withRouter } from 'react-router-dom'

const LiveRoute = withRouter(NotLiveRoute)

2. Props of LiveRoute

livePath: (string | string[])

livePath is the path you want to hide the component instead of unmount it. The specific rules of livePath are the same as path props of Route in react-router-v4. You still can use component or render props to render a component.

livePath also can accept an array of string above since 1.2.0. livePath is matched if any string in the array is matched.

LiveRoute will re-render when it come back from a path matching location from the livePath matching location. It will unmount on other unmatched locations.

Example:

The route of List will be rendered normally under /list, and it will be hidden when location change to /user/:id, and it will be unmounted normally when entering other locations.

import NotLiveRoute from 'react-live-route'
import { withRouter } from 'react-router-dom'

const LiveRoute = withRouter(NotLiveRoute)

<LiveRoute path="/list" livePath="/user/:id" component={List} />

alwaysLive: boolean

alwaysLive is just like livePath. The difference is the component will not be unmount on any other location after the it's first mount.

Example:

After the first mount on match location, the Modal page will be hidden when the path is not matched, and will re-render when path match again.

import NotLiveRoute from 'react-live-route'
import { withRouter } from 'react-router-dom'

const LiveRoute = withRouter(NotLiveRoute)

<LiveRoute path="/list" alwaysLive={true} component={Modal} />

onHide: (location, match, history, livePath, alwaysLive) => void

This hook will be triggered when LiveRoute will hide in componentWillReceiveProps stage (so it happens before re-render).

Example of usage is below.

onReappear: (location, match, history, livePath, alwaysLive) => void

This hook will be triggered when LiveRoute will reappear from hide in componentWillReceiveProps stage (so it happens before re-render).

import NotLiveRoute from 'react-live-route'
import { withRouter } from 'react-router-dom'

const LiveRoute = withRouter(NotLiveRoute)

<LiveRoute
  path="/items"
  component={List}
  livePath="/item/:id"
  name="items"
  onHide={(location, match, livePath, alwaysLive) => {
    console.log('[on hide]')
  }}
  onReappear={(location, match, livePath, alwaysLive) => {
    console.log('[on reappear]')
    console.log(routeState)
  }}
/>

forceUnmount: (location, match, history, livePath, alwaysLive) => boolean

forceUnmount is funtion that return a boolean value to decide weather to forceUnmount the LiveRoute no matter it is matched or should be kept lived.

For example: when the user id equals to 27, List page will be force unmounted while routing on other value of id will be kept.

import NotLiveRoute from 'react-live-route'
import { withRouter } from 'react-router-dom'

const LiveRoute = withRouter(NotLiveRoute)

<LiveRoute path="/list" livePath="/user/:id" component={List} forceUnmount={(location, match)=> match.params.id === 27}/>

ensureDidMount (code-splitting)

ensureDidMount is useful when using a route with react-loadable. Cause react-loadable will load the route component asynchronous. So the route component must give a hint to help react-live-route know when the real component is loaded so the DOM could be got.

const LoadableItems = Loadable({
  loader: () => import("./list"),
  loading: () => () => <p>xxx</p>
})

List item:

  componentDidMount() {
    this.props.ensureDidMount()
  }

TIPS

Use in <Switch>

There's some stuff extra to do when Using with <Switch>. If you want makes /items pathname alive, you must put a placeholder route where it should be but renders nothing. And put LiveRoute out of Switch.

(You have to do this because keep-alive break the origin <Switch> or React philosophy so we have use a placeholder route to keep the philosophy and show the real content out of .)

function App() {
  return (
    <div className="App">
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/item/:id" component={Detail} />
        <Route path="/about" component={About} />
        <Route path="/items" /> // placeholder(required)
        <Route path="*" render={NotFound} />
      </Switch>
      <LiveRoute
        path="/items"
        component={List}
        livePath="/item/:id"
        name="items"
        onHide={routeState => {
          console.log("[on hide]");
          console.log(routeState);
        }}
        onReappear={routeState => {
          console.log("[on reappear]");
          console.log(routeState);
        }}
      />
      <Bar />
    </div>
  );
}

Licence

MIT

react-live-route's People

Contributors

dependabot[bot] avatar fi3ework avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

react-live-route's Issues

关于滚动恢复

使用了推荐方法,从一个长页面,进入一个短的页面后,端页面会过长,下面很多留白,请问该怎么处理呢

页面加载问题

我有A/B/C三页,首次进入加载A页,点击切换到B页时,A页不会隐藏,B页显示在A页下方,接着打开C页,C页会显示在B页下方;重新再点击一次A/B/c页后会恢复正常,求解这是什么原因,已使用this.props.ensureDidMount()

cant restore position when using react-live-route with better-scroll

Update:
There are some mistakes in the former description, i used react-bscroll instead of better-scroll。
Sorry for that~ But it seems that they encounter the same problems~
Description:
when using better-scroll libs, it cant restore position?
it seems better-scroll using CSS attribute "transform" to scroll , but react-live-route using scrollTo to scroll 。
Of course it cant get scroll position using scrollTop or scrollLeft

Expectation:
1.when route back ,it restores right position

onReappear 并没有执行

看文档也没有说onReappear是放在路由里还是放在页面里面,不过两个地方都试过没有效果,根本不会触发onReappear。

页面路由信息获取报错

当前在A页面/userCenter/0,正常渲染,组件内有方法依赖于this.props.match.params.id ,当隐藏时触发render内的依赖id的方法,此时到B页面无参数,获取this.props.match为null。有什么方法是监听到当前是隐藏操作么

onHide和onReappear没有执行

将路由信息配置化之后

import React from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import routers from 'routes.js';
import NotLiveRoute from 'react-live-route';
const keepAliveList = routers.filter(d => (d.keepAlive || d.nextPathKeep));
const LiveRoute = withRouter(NotLiveRoute);

class RoutesList extends React.Component {
  render() {
    let constRoute = routers.map((item, index) => {
      let props = {};
      if (!item.keepAlive) {
        props = {
          ...props,
          component: item.component
        };
      }
      return (
        <Route exact={true} key={index} path={item.path} {...props} name={item.name}/>
      );
    });
    let LiveRoutes = keepAliveList.map((item, index) => {
      let props = {};
      if (item.keepAlive) {
        props = {
          ...props,
          alwaysLive: true
        };
      } else if (item.alwaysLive) {
        props = {
          ...props,
          livePath: item.nextPathKeep
        };
      }
      return (
        <LiveRoute
          {...props}
          exact={true}
          key={item.name}
          component={item.component}
          path={item.path}
          name={item.name}
          onHide={(location, match, history, livePath, alwaysLive) => {
            console.log(item.name + ': [on reappear]');
            typeof item.Hide === 'function' && item.Hide({ location, match, history, livePath, alwaysLive });
          }}
          onReappear={(location, match, history, livePath, alwaysLive) => {
            console.log(item.name + ': [on reappear]');
            typeof item.onReappear === 'function' && item.onReappear({ location, match, history, livePath, alwaysLive });
          }}
          forceUnmount={(location, match) => {
            return typeof item.forceUnmount === 'function' && item.forceUnmount(location, match);
          }}
        />
      );
    });
    return (
      <div className='routes'>
        <Switch>
          {constRoute}
        </Switch>
        {LiveRoutes}
      </div>
    );
  }
}

export default RoutesList;

没有执行任何onHide或者onReappear的事件
你们的这个例子也没有打console.log(arguments) 请问是什么情况呢
官方例子

its not ‘display:none’ on the old page, when the first pushState.

first, i open the List view, and the List view route is setting 'keep-alive';
then i enter into Detail view;

the List view is not be ummounted, thats true.
but the List view is not in 'display: none'.
so there are two component on Detail view...

after I goback the List view and enter Detail view again, this problem is not exist(List view is display: none).

怎么实现点击关闭tab功能

如果根据文档中的api,如果要实现点击,打开一个tab页,切换tab页保持缓存,那是没有问题的。

但是考虑到点击tab页上的关闭,要卸载掉路由,文档里的forceUnmount,在代码中并没有找到,不知是否已经删除了这个api。

而且似乎以下的场景也还是没法实现(没有代码无法看实现细节):
/user/:id路由为这个,然后打开了三个tab页,分别是/user/1, /user/2, /user/3,点击关闭3,如果使用forceUnmount去匹配3时卸载(通过mobx之类的中转值),可是整个LiveRoute不是应该都会卸载吗?那1和2的还会存在吗

如果A页面很长,那么跳转到B页面时,滚动条并非在顶部

如果A页面有3个屏幕高度,然后跳转到B页面的链接恰好也在页面最后
<br />比如A页面很长很长<br /><br /><br /><br />这个是在页面底部的链接<Link to="/b" >---去B页面---</Link>
如上代码,那么显示B页面时(恰好B页面也很长很长),滚动条并非在顶部,所以问题就是:B页面的滚动条有办法一开始就在顶部吗?

还有一个疑问是,这个插件支持延迟加载组件吗?就是React.lazy搭配Suspense
我发现用了延迟加载的话,第一次后退不会有display:none,要第2次才会display:none

[Discussion]Should the apps stop instantiating when show/hide the dom?

Description

1.Why should the app return a component instance when matched(no mater match path or always alive).

return children && !isEmptyChildren(children)
      ? children
      : matchAnyway
      ? component
        ? componentInstance
        : render
        ? render(props as any)
        : null
      : null

In my opinion, from hide -> show, the very only method is performing SHOW_DOM.But in fact , when i use return null, it will not show or hide dom, but destroyed the dom.

if (matchOfPath) {
      debugLog('--- normal match —‘)
     //***//
    } else {
      debugLog('--- hide match ---')
      // force unmount

      // show ➡️ hide
      if (this.liveState === LiveState.NORMAL_RENDER_MATCHED) {
        this.currentSideEffect = [SideEffect.ON_HIDE_HOOK, SideEffect.SAVE_DOM_SCROLL, SideEffect.HIDE_DOM]
      }
      this.liveState = LiveState.HIDE_RENDER
      **return null**
    }

2.Why should the app instantiate with props using 'match: matchOfPath'?
What does it used for? I use 'this.props' instead of 'props', it still works.

    // normal render
    const props = { ...context, location, match: matchOfPath, ensureDidMount: this.getRouteDom }
    const componentInstance = component && React.createElement(component, props)

请问返回时,有没有钩子函数

我的需求是:查询=>修改,修改完成后再返回“查询”时,能有一个地方,可以利用现有的缓存的搜索条件,自动执行一次查询。

iframe组件缓存失败

  getRouteDom() {
    let routeDom = ReactDOM.findDOMNode(this)
    this.routeDom = routeDom
  }

应该是iframe组件加载时间较长,componentDidMount后直接取不到routeDom,导致缓存失败,测试加上setTimeout后可以了,组件代码:

    return (
      <Page inner style={{ float: 'left', width: '100%', height: '100%' }} >
        <iframe title="test" src="http://www.sina.com.cn" />
      </Page>
    )

Live-Route为啥不能写在Switch里面去?

其实一直以来我都有一个问题,这个Live-Route组件为啥不能写在Switch里面和正常的Route组件一样呢?而要在Switch里面多加一个Component为空的Route?

关于路由传参获取不到请教

路由配置:
[{
path: '/a',
component: Hello,
name: "你好"
},{
path: '/b/:id',
component: Hello,
name: "你好"
}]
liveroute使用:
<LiveRoute key={index} path={item.path} exact alwaysLive={true}
forceUnmount={(props, params) => this.state.tab_close == item.path}
render={props => (
<Suspense fallback={} key={index}>
<item.component {...props} />

)}>

路由从a页面跳转至b页面时,传递了参数id,/b/1 , 在对应的b页面使用this.props.match.params.id 取到的值为undefined
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.