Phát hiện người dùng rời trang bằng bộ định tuyến phản ứng


115

Tôi muốn ứng dụng ReactJS của mình thông báo cho người dùng khi điều hướng khỏi một trang cụ thể. Cụ thể là một thông báo bật lên nhắc nhở anh ấy / cô ấy thực hiện một hành động:

"Các thay đổi đã được lưu, nhưng chưa được xuất bản. Làm ngay bây giờ?"

Tôi có nên kích hoạt điều này trên react-routertoàn cầu hay đây là điều gì đó có thể được thực hiện từ bên trong trang / thành phần phản ứng?

Tôi không tìm thấy gì ở cái sau, và tôi thà tránh cái đầu tiên. Tất nhiên, trừ khi nó là tiêu chuẩn, nhưng điều đó khiến tôi tự hỏi làm thế nào để làm điều đó mà không cần phải thêm mã vào mọi trang có thể khác mà người dùng có thể truy cập ..

Mọi thông tin chi tiết đều được chào đón, cảm ơn!


3
Tôi không làm nếu đây là những gì bạn đang tìm kiếm, nhưng bạn có thể làm được. như thế này componentWillUnmount() { if (confirm('Changes are saved, but not published yet. Do that now?')) { // publish and go away from a specific page } else { // do nothing and go away from a specific page } }, do đó bạn có thể gọi bạn xuất bản chức năng bevor rời khỏi trang
Rico Berger

Câu trả lời:


153

react-routerv4 giới thiệu một cách mới để chặn điều hướng bằng cách sử dụng Prompt. Chỉ cần thêm điều này vào thành phần mà bạn muốn chặn:

import { Prompt } from 'react-router'

const MyComponent = () => (
  <React.Fragment>
    <Prompt
      when={shouldBlockNavigation}
      message='You have unsaved changes, are you sure you want to leave?'
    />
    {/* Component JSX */}
  </React.Fragment>
)

Điều này sẽ chặn bất kỳ định tuyến nào, nhưng không chặn làm mới hoặc đóng trang. Để chặn điều đó, bạn sẽ cần thêm cái này (cập nhật nếu cần với vòng đời React thích hợp):

componentDidUpdate = () => {
  if (shouldBlockNavigation) {
    window.onbeforeunload = () => true
  } else {
    window.onbeforeunload = undefined
  }
}

onbeforeunload có nhiều trình duyệt hỗ trợ.


9
Điều này dẫn đến hai cảnh báo trông rất khác nhau.
XanderStrike

3
@XanderStrike Bạn có thể thử tạo kiểu cảnh báo nhắc để bắt chước cảnh báo mặc định của trình duyệt. Thật không may, không có cách nào để tạo kiểu onberforeunloadcảnh báo.
jcady

Có cách nào để hiển thị cùng một thành phần Nhắc khi người dùng nhấp vào nút HỦY không?
Rene Enriquez

1
@ReneEnriquez react-routerkhông hỗ trợ điều đó ngoài hộp (giả sử nút hủy không kích hoạt bất kỳ thay đổi tuyến đường nào). Tuy nhiên, bạn có thể tạo phương thức của riêng mình để bắt chước hành vi.
jcady

6
Nếu bạn kết thúc việc sử dụng onbeforeunload , bạn sẽ muốn dọn dẹp nó khi thành phần của bạn ngắt kết nối. componentWillUnmount() { window.onbeforeunload = null; }
cdeutsch

40

Trong bộ định tuyến phản ứng v2.4.0trở lên và trước khi v4có một số tùy chọn

  1. Thêm chức năng onLeavechoRoute
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
  1. Sử dụng chức năng setRouteLeaveHookchocomponentDidMount

Bạn có thể ngăn quá trình chuyển đổi xảy ra hoặc nhắc người dùng trước khi rời khỏi tuyến đường bằng dấu móc rời.

const Home = withRouter(
  React.createClass({

    componentDidMount() {
      this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
    },

    routerWillLeave(nextLocation) {
      // return false to prevent a transition w/o prompting the user,
      // or return a string to allow the user to decide:
      // return `null` or nothing to let other hooks to be executed
      //
      // NOTE: if you return true, other hooks will not be executed!
      if (!this.state.isSaved)
        return 'Your work is not saved! Are you sure you want to leave?'
    },

    // ...

  })
)

Lưu ý rằng ví dụ này sử dụng thành phần withRouterbậc cao hơn được giới thiệu trongv2.4.0.

Tuy nhiên, giải pháp này không hoạt động hoàn hảo khi thay đổi tuyến đường trong URL theo cách thủ công

Theo nghĩa rằng

  • chúng tôi thấy Xác nhận - ok
  • chứa của trang không tải lại - ok
  • URL không thay đổi - không ổn

Để react-router v4sử dụng Lời nhắc hoặc lịch sử tùy chỉnh:

Tuy nhiên react-router v4, nó khá dễ thực hiện hơn với sự trợ giúp của Promptbộ định tuyến chính xác

Theo tài liệu

Lời nhắc

Được sử dụng để nhắc người dùng trước khi điều hướng khỏi một trang. Khi ứng dụng của bạn chuyển sang trạng thái khiến người dùng không thể điều hướng đi (giống như biểu mẫu được điền một nửa), hãy hiển thị a <Prompt>.

import { Prompt } from 'react-router'

<Prompt
  when={formIsHalfFilledOut}
  message="Are you sure you want to leave?"
/>

tin nhắn: chuỗi

Thông báo để nhắc người dùng khi họ cố gắng điều hướng đi.

<Prompt message="Are you sure you want to leave?"/>

tin nhắn: func

Sẽ được gọi với vị trí và hành động tiếp theo mà người dùng đang cố gắng điều hướng đến. Trả về một chuỗi để hiển thị lời nhắc cho người dùng hoặc true để cho phép chuyển đổi.

<Prompt message={location => (
  `Are you sure you want to go to ${location.pathname}?`
)}/>

khi nào: bool

Thay vì hiển thị có điều kiện <Prompt>một người bảo vệ phía sau, bạn luôn có thể hiển thị nó nhưng vượt qua when={true}hoặc when={false}để ngăn hoặc cho phép điều hướng tương ứng.

Trong phương thức kết xuất của bạn, bạn chỉ cần thêm điều này như đã đề cập trong tài liệu tùy theo nhu cầu của bạn.

CẬP NHẬT:

Trong trường hợp bạn muốn có một hành động tùy chỉnh để thực hiện khi việc sử dụng đang rời khỏi trang, bạn có thể sử dụng lịch sử tùy chỉnh và định cấu hình Bộ định tuyến của mình như

history.js

import createBrowserHistory from 'history/createBrowserHistory'
export const history = createBrowserHistory()

... 
import { history } from 'path/to/history';
<Router history={history}>
  <App/>
</Router>

và sau đó trong thành phần của bạn, bạn có thể tận dụng history.blocknhư

import { history } from 'path/to/history';
class MyComponent extends React.Component {
   componentDidMount() {
      this.unblock = history.block(targetLocation => {
           // take your action here     
           return false;
      });
   }
   componentWillUnmount() {
      this.unblock();
   }
   render() {
      //component render here
   }
}

1
Ngay cả khi bạn sử dụng <Prompt>, URL vẫn bị thay đổi khi bạn nhấn Hủy trên lời nhắc. Vấn đề liên quan: github.com/ReactTraining/react-router/issues/5405
Sahan Serasinghe

1
Tôi thấy câu hỏi này vẫn còn khá phổ biến. Vì các câu trả lời hàng đầu dành cho các phiên bản cũ không dùng nữa, tôi đặt câu trả lời mới hơn này làm câu trả lời được chấp nhận. Các liên kết đến tài liệu cũ (và hướng dẫn nâng cấp) bây giờ có sẵn tại github.com/ReactTraining/react-router
Barry Staes

1
onLeave được kích hoạt SAU KHI thay đổi tuyến đường được xác nhận. Bạn có thể nói rõ hơn về cách hủy điều hướng trong OnLeave không?
schlingel

23

Đối với react-router2.4.0+

LƯU Ý : Bạn nên di chuyển tất cả mã của mình sang phiên bản mới nhất react-routerđể nhận được tất cả các tính năng mới.

Như được đề xuất trong tài liệu bộ định tuyến phản ứng :

Người ta nên sử dụng withRouterthành phần thứ tự cao hơn:

Chúng tôi nghĩ rằng HoC mới này đẹp hơn và dễ dàng hơn, đồng thời sẽ sử dụng nó trong tài liệu và ví dụ, nhưng không phải là một yêu cầu khó để chuyển đổi.

Như một ví dụ về ES6 từ tài liệu:

import React from 'react'
import { withRouter } from 'react-router'

const Page = React.createClass({

  componentDidMount() {
    this.props.router.setRouteLeaveHook(this.props.route, () => {
      if (this.state.unsaved)
        return 'You have unsaved information, are you sure you want to leave this page?'
    })
  }

  render() {
    return <div>Stuff</div>
  }

})

export default withRouter(Page)

4
Gọi lại RouteLeaveHook làm gì? Nó có nhắc người dùng sử dụng phương thức cài sẵn không? Điều gì sẽ xảy ra nếu bạn muốn một phương thức tùy chỉnh
Người học

Giống như @Learner: Làm cách nào để thực hiện việc này với hộp xác nhận tùy chỉnh như vex hoặc SweetAlert hoặc hộp thoại không chặn khác?
olefrank

Tôi đang gặp lỗi sau: - TypeError: Không thể đọc thuộc tính ' id ' của không xác định. Bất cứ đề nghị
Mustafa Mamun

@MustafaMamun Đó dường như là một vấn đề không liên quan và hầu như bạn sẽ muốn tạo một câu hỏi mới giải thích chi tiết.
snymkpr

Tôi đã gặp sự cố khi cố gắng sử dụng giải pháp. Thành phần phải được kết nối trực tiếp với bộ định tuyến thì giải pháp này mới hoạt động. Tôi tìm thấy một tốt hơn trả lời có stackoverflow.com/questions/39103684/...
Mustafa Mamun

4

Đối với react-routerv3.x

Tôi đã gặp vấn đề tương tự trong đó tôi cần một thông báo xác nhận cho bất kỳ thay đổi nào chưa được lưu trên trang. Trong trường hợp của tôi, tôi đang sử dụng React Router v3 , vì vậy tôi không thể sử dụng <Prompt />, được giới thiệu từ React Router v4 .

Tôi đã xử lý 'nhấp vào nút quay lại' và 'nhấp vào liên kết tình cờ' bằng sự kết hợp của setRouteLeaveHookhistory.pushState()và xử lý 'nút tải lại' với onbeforeunloadtrình xử lý sự kiện.

setRouteLeaveHook ( doc ) & history.pushState ( doc )

  • Chỉ sử dụng setRouteLeaveHook là không đủ. Vì một số lý do, URL đã bị thay đổi mặc dù trang vẫn giữ nguyên khi nhấp vào 'nút quay lại'.

      // setRouteLeaveHook returns the unregister method
      this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
        this.props.route,
        this.routerWillLeave
      );
    
      ...
    
      routerWillLeave = nextLocation => {
        // Using native 'confirm' method to show confirmation message
        const result = confirm('Unsaved work will be lost');
        if (result) {
          // navigation confirmed
          return true;
        } else {
          // navigation canceled, pushing the previous path
          window.history.pushState(null, null, this.props.route.path);
          return false;
        }
      };

onbeforeunload ( doc )

  • Nó được sử dụng để xử lý nút 'tải lại tình cờ'

    window.onbeforeunload = this.handleOnBeforeUnload;
    
    ...
    
    handleOnBeforeUnload = e => {
      const message = 'Are you sure?';
      e.returnValue = message;
      return message;
    }

Dưới đây là thành phần đầy đủ mà tôi đã viết

  • lưu ý rằng withRouter được sử dụng để có this.props.router.
  • lưu ý this.props.routeđược truyền lại từ thành phần gọi
  • lưu ý rằng currentStateđược chuyển như là chỗ dựa để có trạng thái ban đầu và để kiểm tra bất kỳ thay đổi nào

    import React from 'react';
    import PropTypes from 'prop-types';
    import _ from 'lodash';
    import { withRouter } from 'react-router';
    import Component from '../Component';
    import styles from './PreventRouteChange.css';
    
    class PreventRouteChange extends Component {
      constructor(props) {
        super(props);
        this.state = {
          // initialize the initial state to check any change
          initialState: _.cloneDeep(props.currentState),
          hookMounted: false
        };
      }
    
      componentDidUpdate() {
    
       // I used the library called 'lodash'
       // but you can use your own way to check any unsaved changed
        const unsaved = !_.isEqual(
          this.state.initialState,
          this.props.currentState
        );
    
        if (!unsaved && this.state.hookMounted) {
          // unregister hooks
          this.setState({ hookMounted: false });
          this.unregisterRouteHook();
          window.onbeforeunload = null;
        } else if (unsaved && !this.state.hookMounted) {
          // register hooks
          this.setState({ hookMounted: true });
          this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
            this.props.route,
            this.routerWillLeave
          );
          window.onbeforeunload = this.handleOnBeforeUnload;
        }
      }
    
      componentWillUnmount() {
        // unregister onbeforeunload event handler
        window.onbeforeunload = null;
      }
    
      handleOnBeforeUnload = e => {
        const message = 'Are you sure?';
        e.returnValue = message;
        return message;
      };
    
      routerWillLeave = nextLocation => {
        const result = confirm('Unsaved work will be lost');
        if (result) {
          return true;
        } else {
          window.history.pushState(null, null, this.props.route.path);
          if (this.formStartEle) {
            this.moveTo.move(this.formStartEle);
          }
          return false;
        }
      };
    
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        );
      }
    }
    
    PreventRouteChange.propTypes = propTypes;
    
    export default withRouter(PreventRouteChange);

Vui lòng cho tôi biết nếu có bất kỳ câu hỏi nào :)


Đây chính xác là đâu .formStartEle đến từ?
Gaurav

2

Đối với react-routerv0.13.x với reactv0.13.x:

điều này có thể thực hiện được với các phương thức willTransitionTo()willTransitionFrom()static. Đối với các phiên bản mới hơn, hãy xem câu trả lời khác của tôi bên dưới.

Từ tài liệu bộ định tuyến phản ứng :

Bạn có thể xác định một số phương thức tĩnh trên trình xử lý tuyến đường của bạn sẽ được gọi trong quá trình chuyển đổi tuyến đường.

willTransitionTo(transition, params, query, callback)

Được gọi khi một trình xử lý sắp kết xuất, cho bạn cơ hội hủy bỏ hoặc chuyển hướng quá trình chuyển đổi. Bạn có thể tạm dừng quá trình chuyển đổi trong khi thực hiện một số công việc không liên tục và gọi lệnh gọi lại (lỗi) khi bạn hoàn thành hoặc bỏ qua lệnh gọi lại trong danh sách đối số của bạn và nó sẽ được gọi cho bạn.

willTransitionFrom(transition, component, callback)

Được gọi khi một tuyến đường đang hoạt động đang được chuyển đổi cho bạn cơ hội hủy bỏ quá trình chuyển đổi. Thành phần là thành phần hiện tại, có thể bạn sẽ cần kiểm tra trạng thái của nó để quyết định xem bạn có muốn cho phép chuyển đổi (như các trường biểu mẫu) hay không.

Thí dụ

  var Settings = React.createClass({
    statics: {
      willTransitionTo: function (transition, params, query, callback) {
        auth.isLoggedIn((isLoggedIn) => {
          transition.abort();
          callback();
        });
      },

      willTransitionFrom: function (transition, component) {
        if (component.formHasUnsavedData()) {
          if (!confirm('You have unsaved information,'+
                       'are you sure you want to leave this page?')) {
            transition.abort();
          }
        }
      }
    }

    //...
  });

Đối với react-router1.0.0-rc1 với reactv0.14.x trở lên:

điều này có thể thực hiện được với routerWillLeavemóc vòng đời. Đối với các phiên bản cũ hơn, hãy xem câu trả lời của tôi ở trên.

Từ tài liệu bộ định tuyến phản ứng :

Để cài đặt hook này, hãy sử dụng Lifecycle mixin trong một trong các thành phần tuyến của bạn.

  import { Lifecycle } from 'react-router'

  const Home = React.createClass({

    // Assuming Home is a route component, it may use the
    // Lifecycle mixin to get a routerWillLeave method.
    mixins: [ Lifecycle ],

    routerWillLeave(nextLocation) {
      if (!this.state.isSaved)
        return 'Your work is not saved! Are you sure you want to leave?'
    },

    // ...

  })

Nhiều thứ. có thể thay đổi trước khi phát hành cuối cùng.


1

Bạn có thể sử dụng lời nhắc này.

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link, Prompt } from "react-router-dom";

function PreventingTransitionsExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Form</Link>
          </li>
          <li>
            <Link to="/one">One</Link>
          </li>
          <li>
            <Link to="/two">Two</Link>
          </li>
        </ul>
        <Route path="/" exact component={Form} />
        <Route path="/one" render={() => <h3>One</h3>} />
        <Route path="/two" render={() => <h3>Two</h3>} />
      </div>
    </Router>
  );
}

class Form extends Component {
  state = { isBlocking: false };

  render() {
    let { isBlocking } = this.state;

    return (
      <form
        onSubmit={event => {
          event.preventDefault();
          event.target.reset();
          this.setState({
            isBlocking: false
          });
        }}
      >
        <Prompt
          when={isBlocking}
          message={location =>
            `Are you sure you want to go to ${location.pathname}`
          }
        />

        <p>
          Blocking?{" "}
          {isBlocking ? "Yes, click a link or the back button" : "Nope"}
        </p>

        <p>
          <input
            size="50"
            placeholder="type something to block transitions"
            onChange={event => {
              this.setState({
                isBlocking: event.target.value.length > 0
              });
            }}
          />
        </p>

        <p>
          <button>Submit to stop blocking</button>
        </p>
      </form>
    );
  }
}

export default PreventingTransitionsExample;

1

Sử dụng history.listen

Ví dụ như dưới đây:

Trong thành phần của bạn,

componentWillMount() {
    this.props.history.listen(() => {
      // Detecting, user has changed URL
      console.info(this.props.history.location.pathname);
    });
}

5
Xin chào, chào mừng bạn đến với Stack Overflow. Khi trả lời một câu hỏi đã có nhiều câu trả lời, hãy đảm bảo thêm một số thông tin chi tiết bổ sung về lý do tại sao câu trả lời bạn đang cung cấp là thực chất và không chỉ lặp lại những gì đã được người đăng gốc kiểm tra. Điều này đặc biệt quan trọng trong các câu trả lời "chỉ có mã", chẳng hạn như câu bạn đã cung cấp.
chb
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.