在 React-router 中拦截/处理浏览器的后退按钮?

问题描述 投票:0回答:16

我正在使用 Material-ui 的选项卡,这些选项卡是受控的,我将它们用于(React-router)链接,如下所示:

    <Tab value={0} label="dashboard" containerElement={<Link to="/dashboard/home"/>}/>
    <Tab value={1} label="users" containerElement={<Link to="/dashboard/users"/>} />
  <Tab value={2} label="data" containerElement={<Link to="/dashboard/data"/>} />

如果我当前正在访问仪表板/数据并单击浏览器的后退按钮 我(例如)转到仪表板/用户,但突出显示的选项卡仍保留在仪表板/数据上(值=2)

我可以通过设置状态来改变,但是我不知道如何处理按下浏览器后退按钮的事件?

我发现了这个:

window.onpopstate = this.onBackButtonEvent;

但是每次状态改变时都会调用它(不仅在后退按钮事件上)

javascript reactjs react-router material-ui
16个回答
62
投票

使用 react-router 使工作变得如此简单:

import { browserHistory } from 'react-router';

componentDidMount() {
    this.onScrollNearBottom(this.scrollToLoad);

    this.backListener = browserHistory.listen((loc, action) => {
      if (action === "POP") {
        // Do your stuff
      }
    });
  }

componentWillUnmount() {
    // Unbind listener
    this.backListener();
}

51
投票

使用钩子可以检测后退和前进按钮

import { useHistory } from 'react-router-dom'


const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()

useEffect(() => {
  return history.listen(location => {
    if (history.action === 'PUSH') {
      setLocationKeys([ location.key ])
    }

    if (history.action === 'POP') {
      if (locationKeys[1] === location.key) {
        setLocationKeys(([ _, ...keys ]) => keys)

        // Handle forward event

      } else {
        setLocationKeys((keys) => [ location.key, ...keys ])

        // Handle back event

      }
    }
  })
}, [ locationKeys, ])

24
投票

这是我最终的做法:

componentDidMount() {
    this._isMounted = true;
    window.onpopstate = ()=> {
      if(this._isMounted) {
        const { hash } = location;
        if(hash.indexOf('home')>-1 && this.state.value!==0)
          this.setState({value: 0})
        if(hash.indexOf('users')>-1 && this.state.value!==1)
          this.setState({value: 1})
        if(hash.indexOf('data')>-1 && this.state.value!==2)
          this.setState({value: 2})
      }
    }
  }

谢谢大家的帮助哈哈


23
投票

挂钩样品

const {history} = useRouter();
  useEffect(() => {
    return () => {
      // && history.location.pathname === "any specific path")
      if (history.action === "POP") {
        history.replace(history.location.pathname, /* the new state */);
      }
    };
  }, [history])

我不使用 history.listen 因为它不影响状态

const disposeListener = history.listen(navData => {
        if (navData.pathname === "/props") {
            navData.state = /* the new state */;
        }
    });

12
投票

这个问题的大部分答案要么使用过时版本的 React Router,要么依赖不太现代的类组件,要么令人困惑;并且没有人使用 Typescript,这是一种常见的组合。这是使用 Router v5、函数组件和 Typescript 的答案:

// use destructuring to access the history property of the ReactComponentProps type
function MyComponent( { history }: ReactComponentProps) {

    // use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
    useEffect(() => {

        return () => {
            if (history.action === "POP") {
                // Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
                }
           }
    })
}

可以在这里找到一个更完整的例子和解释。


9
投票

React Router API 的 3.x 版具有 一组实用程序 可用于在事件注册到浏览器的历史记录之前公开“后退”按钮事件。您必须首先将您的组件包装在

withRouter()
高阶组件 中。然后,您可以使用
setRouteLeaveHook()
函数,该函数接受任何具有有效
route
属性和回调函数的
path
对象。

import {Component} from 'react';
import {withRouter} from 'react-router';

class Foo extends Component {
  componentDidMount() {
    this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
  }

  routerWillLeave(nextState) { // return false to block navigation, true to allow
    if (nextState.action === 'POP') {
      // handle "Back" button clicks here
    }
  }
}

export default withRouter(Foo);

8
投票

使用钩子。我已将@Nicolas Keller 的代码转换为打字稿

  const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
  const history = useHistory();

  useEffect(() => {
    return history.listen((location) => {
      if (history.action === 'PUSH') {
        if (location.key) setLocationKeys([location.key]);
      }

      if (history.action === 'POP') {
        if (locationKeys[1] === location.key) {
          setLocationKeys(([_, ...keys]) => keys);

          // Handle forward event
          console.log('forward button');
        } else {
          setLocationKeys((keys) => [location.key, ...keys]);

          // Handle back event
          console.log('back button');
          removeTask();
        }
      }
    });
  }, [locationKeys]);

4
投票

我使用 withrouter hoc 来获取 history prop 并编写一个 componentDidMount() 方法:

componentDidMount() {
    if (this.props.history.action === "POP") {
        // custom back button implementation
    }
}

4
投票

在 NextJs 中,我们可以使用 beforePopState 函数并执行我们想要的操作,例如关闭模态或显示模态或检查后退地址并决定要做什么

const router = useRouter();

useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
        // I only want to allow these two routes!

        if (as === '/' ) {
            // Have SSR render bad routes as a 404.
             window.location.href = as;
            closeModal();
            return false
        }

        return true
    })
}, [])

3
投票

用于在响应功能组件中对浏览器的按下发出警告。执行以下步骤

  1. 声明 isBackButtonClicked 并将其初始化为 false 并使用 setBackbuttonPress 函数维护状态。
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
  1. 在componentdidmount中,添加如下几行代码
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);
  1. 定义 onBackButtonEvent 函数并根据您的要求编写逻辑。

      const onBackButtonEvent = (e) => {
      e.preventDefault();
      if (!isBackButtonClicked) {
    
      if (window.confirm("Do you want to go to Test Listing")) {
        setBackbuttonPress(true)
        props.history.go(listingpage)
      } else {
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      }
    }
    

    }

  2. 在componentwillmount unsubscribe onBackButtonEvent函数

最终代码看起来像这样

import React,{useEffect,useState} from 'react'

function HandleBrowserBackButton() {
  const [isBackButtonClicked, setBackbuttonPress] = useState(false)

  useEffect(() => {

    window.history.pushState(null, null, window.location.pathname);
    window.addEventListener('popstate', onBackButtonEvent);

    //logic for showing popup warning on page refresh
    window.onbeforeunload = function () {

      return "Data will be lost if you leave the page, are you sure?";
    };
    return () => {
      window.removeEventListener('popstate', onBackButtonEvent);
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  const onBackButtonEvent = (e) => {
    e.preventDefault();
    if (!isBackButtonClicked) {

      if (window.confirm("Do you want to go to Test Listing")) {
        setBackbuttonPress(true)
        props.history.go(listingpage)
      } else {
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      }
    }
  }

  return (
    <div>

    </div>
  )
}

export default HandleBrowserBackButton

3
投票

如果你正在使用 React Router V5,你可以试试 Prompt.

用于在离开页面之前提示用户。当您的应用程序进入应阻止用户离开的状态时(例如表单填写了一半),呈现一个

<Prompt
   message={(location, action) => {
   if (action === 'POP') {
      console.log("Backing up...")
      // Add your back logic here
   }

   return true;
   }}
/>

2
投票

只需放入 componentDidMount()

componentDidMount() {
    window.onbeforeunload =this.beforeUnloadListener;
} 
beforeUnloadListener = (event) => {
    event.preventDefault();
    return event.returnValue = "Are you sure you want to exit?";
};


2
投票

将这两行添加到您的 componentDidMount() 中。这对我有用

window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) {
      window.location.replace(
        `YOUR URL`
      );
});

1
投票

这取决于你在 React 中使用的 Router 类型。

如果您使用 react-router 中的 BrowserRouter(虽然在 react-router v4 中不可用),如上所述,您可以使用操作“POP”拦截浏览器后退按钮。

但是,如果您使用 HashRouter 来推送路由,上述解决方案将不起作用。原因是当您单击浏览器后退按钮或从您的组件推送路由时,哈希路由器总是通过“POP”操作触发。你不能简单地用 window.popstate 或 history.listen 来区分这两个动作。


-1
投票

即将发布的 6.0 版引入了 useBlocker 钩子 - 可用于拦截所有导航尝试。

import { Action } from 'history';
import { useBlocker } from 'react-router';

// when blocker should be active
const unsavedChanges = true;

useBlocker((transition) => {
    const {
        location, // The new location
        action, // The action that triggered the change
    } = transition;

    // intercept back and forward actions:
    if (action === Action.Pop) {
        alert('intercepted!')
    }

}, unsavedChanges);

-7
投票

您可以使用“withrouter”HOC并使用

this.props.history.goBack
.

<Button onClick={this.props.history.goBack}>
    BACK
</Button>
© www.soinside.com 2019 - 2024. All rights reserved.