react-router로 페이지를 떠나는 사용자 감지


116

특정 페이지에서 이동할 때 ReactJS 앱이 사용자에게 알리기를 원합니다. 특히 조치를 취하도록 상기시키는 팝업 메시지 :

"변경 사항이 저장되었지만 아직 게시되지 않았습니다. 지금 수행 하시겠습니까?"

이를 react-router전역 적으로 트리거해야합니까 , 아니면 반응 페이지 / 구성 요소 내에서 수행 할 수 있습니까?

나는 후자에서 아무것도 찾지 못했고 차라리 첫 번째를 피하고 싶습니다. 물론 표준이 아니라면 사용자가 이동할 수있는 다른 모든 페이지에 코드를 추가하지 않고도 이러한 작업을 수행하는 방법이 궁금합니다.

모든 통찰력을 환영합니다, 감사합니다!


3
이것이 당신이 찾고있는 것이면 지금은 아니지만 sth를 할 수 있습니다. 이런 식으로 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 } }당신은 당신의 호출 할 수 있도록 페이지를 떠날 필요입니다 기능을 게시
리코 버거

답변:


154

react-routerv4는를 사용하여 탐색을 차단하는 새로운 방법을 도입했습니다 Prompt. 차단하려는 구성 요소에 다음을 추가하십시오.

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>
)

이렇게하면 라우팅이 차단되지만 페이지 새로 고침이나 닫기는 차단되지 않습니다. 이를 차단하려면 다음을 추가해야합니다 (필요에 따라 적절한 React 수명 주기로 업데이트).

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

onbeforeunload 는 브라우저에서 다양한 지원을 제공합니다.


9
이로 인해 두 가지 매우 다른 경고가 발생합니다.
XanderStrike

3
@XanderStrike 브라우저 기본 경고를 모방하도록 프롬프트 경고의 스타일을 지정할 수 있습니다. 안타깝게도 onberforeunload알림 스타일을 지정할 수있는 방법이 없습니다 .
jcady

예를 들어 사용자가 취소 버튼을 클릭하면 동일한 프롬프트 구성 요소를 표시하는 방법이 있습니까?
Rene Enriquez

1
@ReneEnriquez react-router는 기본적으로이를 지원하지 않습니다 (취소 버튼이 경로 변경을 트리거하지 않는다고 가정). 하지만 동작을 모방하기 위해 자신 만의 모달을 만들 수 있습니다.
jcady

6
을 사용 onbeforeunload 하게되면 구성 요소가 마운트 해제 될 때 정리해야합니다. componentWillUnmount() { window.onbeforeunload = null; }
cdeutsch

40

react-router v2.4.0이상에서 v4몇 가지 옵션이 있기 전에

  1. 기능을 추가하기 onLeave위해Route
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
  1. 사용 기능 setRouteLeaveHook에 대한componentDidMount

전환이 발생하지 않도록하거나 나가기 후크를 사용하여 경로를 떠나기 전에 사용자에게 메시지를 표시 할 수 있습니다.

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?'
    },

    // ...

  })
)

이 예제에서는에 withRouter도입 된 고차 구성 요소를 사용합니다.v2.4.0.

그러나 이러한 솔루션은 URL에서 경로를 수동으로 변경할 때 완벽하게 작동하지 않습니다.

의미에서

  • 확인이 표시됩니다.
  • 페이지 포함이 다시로드되지 않음-확인
  • URL이 변경되지 않음-괜찮지 않음

들어 react-router v4프롬프트 또는 사용자 정의 역사를 사용하여 :

그러나에서는 from'react-router react-router v4의 도움으로 구현하기가 다소 쉽습니다.Prompt

문서에 따르면

신속한

페이지에서 이동하기 전에 사용자에게 메시지를 표시하는 데 사용됩니다. 애플리케이션이 사용자가 다른 곳으로 이동하지 못하도록하는 상태가되면 (예 : 양식이 반만 채워져 있음) <Prompt>.

import { Prompt } from 'react-router'

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

메시지 : 문자열

사용자가 다른 곳으로 이동하려고 할 때 묻는 메시지입니다.

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

메시지 : func

사용자가 탐색을 시도하는 다음 위치 및 작업과 함께 호출됩니다. 사용자에게 프롬프트를 표시하려면 문자열을 반환하고 전환을 허용하려면 true를 반환합니다.

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

언제 : bool

<Prompt>가드 뒤를 조건부로 렌더링하는 대신 항상 렌더링 할 수 있지만 그에 따라 통과 when={true}하거나 when={false}탐색을 방지하거나 허용 할 수 있습니다.

렌더링 방법에서 필요에 따라 문서에 언급 된대로이를 추가하기 만하면됩니다.

최신 정보:

페이지를 사용할 때 사용자 지정 작업을 수행하려면 사용자 지정 기록을 사용하고 다음과 같이 라우터를 구성 할 수 있습니다.

history.js

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

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

그런 다음 구성 요소에서 다음 history.block과 같이 사용할 수 있습니다.

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
<Prompt>URL 을 사용하더라도 프롬프트에서 취소를 누르면 URL이 변경됩니다. 관련 문제 : github.com/ReactTraining/react-router/issues/5405
Sahan Serasinghe

1
이 질문은 여전히 ​​꽤 인기가 있습니다. 상위 답변은 더 이상 사용되지 않는 버전에 대한 것이므로이 최신 답변을 허용되는 답변으로 설정했습니다. 이전 문서 (및 업그레이드 가이드)에 대한 링크는 이제 github.com/ReactTraining/react-router
Barry Staes 2018 년

1
onLeave는 경로 변경이 확인 된 후 시작됩니다. onLeave에서 탐색을 취소하는 방법에 대해 자세히 설명해 주시겠습니까?
schlingel

23

대한 react-router2.4.0+

참고 : react-router새로운 기능을 모두 얻으려면 모든 코드를 최신 코드로 마이그레이션하는 것이 좋습니다 .

react-router 문서 에서 권장하는대로 :

withRouter고차 구성 요소를 사용해야합니다 .

우리는이 새로운 HoC가 더 좋고 더 쉬우 며 문서와 예제에서 사용할 것이라고 생각하지만 전환하는 것이 어려운 요구 사항은 아닙니다.

문서의 ES6 예 :

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
RouteLeaveHook 콜백의 기능은 무엇입니까? 내장 모달을 사용하여 사용자에게 메시지를 표시합니까? 당신은 무엇을 사용자 정의 모달 원하는 경우
학습자

@Learner처럼 : vex 또는 SweetAlert 또는 기타 비 차단 대화 상자와 같은 사용자 정의 된 확인 상자를 사용하여이를 수행하는 방법은 무엇입니까?
olefrank

다음 오류가 있습니다.-TypeError : 정의되지 않은 속성 ' id '를 읽을 수 없습니다 . 어떤 제안
무스타파 Mamun

@MustafaMamun 관련없는 문제인 것 같고 대부분 세부 사항을 설명하는 새로운 질문을 만들고 싶을 것입니다.
snymkpr

솔루션을 사용하는 동안 문제가 발생했습니다. 구성 요소를 라우터에 직접 연결해야이 솔루션이 작동합니다. 내가 발견 거기에 더 나은 답변을 stackoverflow.com/questions/39103684/...
무스타파 Mamun

4

대한 react-routerV3.0의

페이지에 저장되지 않은 변경 사항에 대한 확인 메시지가 필요한 동일한 문제가 발생했습니다. 제 경우에는 React Router v3을 사용 하고 있었기 때문에 React Router v4<Prompt /> 에서 도입 한을 사용할 수 없었습니다 .

나는의 조합으로 '뒤로'버튼 클릭 '과'실수로 링크를 클릭 '으로 처리 setRouteLeaveHook하고 history.pushState(), 그리고 함께'새로 고침 버튼을 '처리하는 onbeforeunload이벤트 핸들러.

setRouteLeaveHook ( doc ) 및 history.pushState ( doc )

  • setRouteLeaveHook 만 사용하는 것만으로는 충분하지 않았습니다. 왠지 '뒤로'버튼을 클릭했을 때 페이지는 그대로 유지 되었으나 URL이 변경되었습니다.

      // 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 ( 문서 )

  • '실수 재 장전'버튼 처리에 사용됩니다.

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

아래는 내가 작성한 전체 구성 요소입니다.

  • 참고 withRouter가 가지고하는 데 사용됩니다 this.props.router.
  • 참고 this.props.route호출 구성 요소에서 아래로 전달됩니다
  • 참고 currentState소품으로 전달되어 초기 상태가하고 변화를 확인하기 위해

    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);

질문이 있으면 알려주세요 :)


this.formStartEle은 정확히 어디에서 왔습니까?
Gaurav

2

들어 react-router와 v0.13.x reactv0.13.x :

이는 willTransitionTo()willTransitionFrom()정적 메서드를 사용하여 가능 합니다. 최신 버전은 아래의 다른 답변을 참조하십시오.

로부터 반응-라우터 설명서 :

경로 전환 중에 호출 될 경로 처리기에서 일부 정적 메서드를 정의 할 수 있습니다.

willTransitionTo(transition, params, query, callback)

핸들러가 렌더링 되려고 할 때 호출되어 전환을 중단하거나 리디렉션 할 수 있습니다. 비동기 작업을 수행하는 동안 전환을 일시 중지하고 완료되면 callback (error)을 호출하거나 인수 목록에서 콜백을 생략하면 호출됩니다.

willTransitionFrom(transition, component, callback)

활성 경로가 전환 될 때 호출되어 전환을 중단 할 수 있습니다. 구성 요소는 현재 구성 요소이므로 전환을 허용할지 여부를 결정하기 위해 상태를 확인하는 데 필요할 수 있습니다 (양식 필드 등).

  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();
          }
        }
      }
    }

    //...
  });

들어 react-router1.0.0-RC1 react이후 v0.14.x 또는 :

이것은 routerWillLeave라이프 사이클 후크 로 가능해야합니다 . 이전 버전의 경우 위의 내 대답을 참조하십시오.

로부터 반응-라우터 설명서 :

이 후크를 설치하려면 경로 구성 요소 중 하나에서 수명주기 믹스 인을 사용합니다.

  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?'
    },

    // ...

  })

소지품. 최종 출시 전에 변경 될 수 있습니다.


1

이 프롬프트를 사용할 수 있습니다.

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

history.listen 사용

예를 들면 아래와 같습니다.

구성 요소에서

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

5
안녕하세요, Stack Overflow에 오신 것을 환영합니다. 이미 많은 답변이있는 질문에 답변 할 때 제공하는 답변이 왜 실질적이고 원본 포스터가 이미 심사 한 내용을 반영하지 않는지에 대한 추가 정보를 추가해야합니다. 이것은 귀하가 제공 한 것과 같은 "코드 전용"답변에서 특히 중요합니다.
chb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.