React-router ile sayfadan ayrılan kullanıcıyı algılama


118

ReactJS uygulamamın, belirli bir sayfadan ayrılırken bir kullanıcıyı bilgilendirmesini istiyorum. Özellikle ona bir eylem yapmasını hatırlatan bir açılır mesaj:

"Değişiklikler kaydedildi, ancak henüz yayınlanmadı. Bunu şimdi mi yapacaksınız?"

Bunu react-routerglobal olarak tetiklemeli miyim , yoksa bu, tepki sayfası / bileşeni içinden yapılabilecek bir şey mi?

İkincisi hakkında hiçbir şey bulamadım ve ilkinden kaçınmayı tercih ederim. Tabii ki norm olmadığı sürece, ama bu, kullanıcının gidebileceği diğer tüm olası sayfalara kod eklemek zorunda kalmadan böyle bir şeyi nasıl yapacağımı merak etmeme neden oluyor ..

Herhangi bir görüş hoş geldiniz, teşekkürler!


3
Eğer aradığın buysa şimdi bilmiyorum, ama yapabilirsin. bunun gibi, 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 } }böylece yayınlama işlevinizi sayfadan ayrılırken çağırabilirsiniz
Rico Berger

Yanıtlar:


155

react-routerv4, gezinmeyi engellemenin yeni bir yolunu sunar Prompt. Bunu engellemek istediğiniz bileşene eklemeniz yeterlidir:

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

Bu, herhangi bir yönlendirmeyi engeller ancak sayfa yenilemeyi veya kapatmayı engelleyemez. Bunu engellemek için, bunu eklemeniz gerekir (uygun React yaşam döngüsü ile gerektiği şekilde güncelleme):

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

onbeforeunload tarayıcılar tarafından çeşitli desteğe sahiptir.


9
Bu, iki farklı görünümlü uyarı ile sonuçlanır.
XanderStrike

3
@XanderStrike İstem uyarısını, tarayıcının varsayılan uyarısını taklit edecek şekilde biçimlendirmeyi deneyebilirsiniz. Maalesef, onberforeunloaduyarıyı şekillendirmenin bir yolu yok .
jcady

Örneğin, kullanıcı bir İPTAL düğmesine tıkladığında aynı Sor bileşenini göstermenin herhangi bir yolu var mı?
Rene Enriquez

1
@ReneEnriquez react-routerbunu kutunun dışında desteklemiyor (iptal düğmesinin herhangi bir rota değişikliğini tetiklemediği varsayılarak). Yine de davranışı taklit etmek için kendi modelinizi oluşturabilirsiniz.
jcady

6
Kullanmayı onbeforeunload bırakırsanız, bileşeninizin bağlantısı kesildiğinde onu temizlemek isteyeceksiniz. componentWillUnmount() { window.onbeforeunload = null; }
cdeutsch

40

React-yönlendiricide v2.4.0veya üzerinde ve v4birkaç seçenek olmadan önce

  1. İşlevini ekleyin onLeaveiçinRoute
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
  1. Kullanım fonksiyonu setRouteLeaveHookiçincomponentDidMount

Ayrılma kancası ile bir rotadan ayrılmadan önce bir geçişin olmasını engelleyebilir veya kullanıcıyı uyarabilirsiniz.

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

    // ...

  })
)

Bu örneğin, içinde withRoutertanıtılan yüksek dereceli bileşeni kullandığını unutmayın .v2.4.0.

Ancak, URL'deki rotayı manuel olarak değiştirirken bu çözüm tam olarak çalışmıyor

Anlamda olduğu

  • Onayı görüyoruz - tamam
  • sayfanın içeriği yeniden yüklenmiyor - tamam
  • URL değişmiyor - tamam değil

İçin react-router v4İstemi veya özel geçmişini kullanarak:

Ancak react-router v4, Promptfrom'react-yönlendiricinin yardımıyla uygulanması oldukça kolaydır.

Belgelere göre

Komut istemi

Bir sayfadan ayrılmadan önce kullanıcıyı uyarmak için kullanılır. Uygulamanız, kullanıcının uzaklaşmasını engellemesi gereken bir duruma girdiğinde (bir formun yarı dolu olması gibi), bir <Prompt>.

import { Prompt } from 'react-router'

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

mesaj: dize

Kullanıcıdan uzaklaşmaya çalıştıklarında kullanıcıyı uyaran mesaj.

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

mesaj: func

Kullanıcının gitmeye çalıştığı bir sonraki konum ve eylemle birlikte çağrılacaktır. Kullanıcıya bir bilgi istemi veya geçişe izin vermek için true göstermek için bir dize döndürün.

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

ne zaman: bool

Bunun yerine şartlı bir render <Prompt>bir bekçi arkasında, daima içinde oluşturulur, ancak geçebilir when={true}ya when={false}önlemek veya buna göre yeni baştan tasarlanmıştır.

Oluşturma yönteminizde, ihtiyacınıza göre belgelerde belirtildiği gibi bunu eklemeniz yeterlidir.

GÜNCELLEME:

Kullanım sayfadan ayrılırken gerçekleştirilecek özel bir işlem yapmak istemeniz durumunda, özel geçmişi kullanabilir ve Yönlendiricinizi aşağıdaki gibi yapılandırabilirsiniz.

history.js

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

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

ve sonra bileşeninizde history.blockbeğenmekten yararlanabilirsiniz

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'yi kullansanız bile , istemde İptal'e bastığınızda değiştirilir. İlgili sorun: github.com/ReactTraining/react-router/issues/5405
Sahan Serasinghe

1
Bu sorunun hala oldukça popüler olduğunu görüyorum. En iyi yanıtlar, kullanımdan kaldırılan eski sürümler için olduğundan, bu yeni yanıtı kabul edilen yanıt olarak belirledim. Eski belgelere (ve yükseltme kılavuzlarına) bağlantılar artık github.com/ReactTraining/react-router
Barry Staes

1
onLeave, bir rota değişikliği onaylandıktan SONRA tetiklenir. OnLeave'de bir navigasyonu nasıl iptal edeceğinizi açıklar mısınız?
schlingel

23

için react-router2.4.0+

NOT : Tüm kodunuzu en son sürüme geçirmeniz önerilir.react-router Tüm yeni ürünleri almak için tüm .

React-yönlendirici belgelerinde önerildiği gibi :

Daha withRouteryüksek dereceden bileşen kullanılmalıdır:

Bu yeni HoC'nin daha hoş ve daha kolay olduğunu ve bunu dokümantasyon ve örneklerde kullanacağını düşünüyoruz, ancak geçiş yapmak zor bir gereklilik değil.

Dokümantasyondan bir ES6 örneği olarak:

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 geri araması ne yapar? Kullanıcıyı yerleşik bir model kullanarak yönlendiriyor mu? Ya özel bir model istiyorsanız
Öğrenci

@Learner gibi: Bunu vex veya SweetAlert gibi özelleştirilmiş bir onay kutusu veya diğer engellemeyen iletişim kutusu ile nasıl yapabilirsiniz?
olefrank

Aşağıdaki hatayı alıyorum: - TypeError: Tanımsız'ın ' id ' özelliği okunamıyor . Herhangi bir öneri
Mustafa Mamun

@MustafaMamun Bu ilgisiz bir sorun gibi görünüyor ve çoğunlukla ayrıntıları açıklayan yeni bir soru oluşturmak isteyeceksiniz.
snymkpr

Çözümü kullanmaya çalışırken sorunu yaşıyordum. Bileşenin doğrudan yönlendiriciye bağlanması gerekir, ardından bu çözüm çalışır. Orada daha iyi bir cevap buldum stackoverflow.com/questions/39103684/…
Mustafa Mamun

4

için react-routerv3.x

Sayfadaki kaydedilmemiş herhangi bir değişiklik için bir onay mesajına ihtiyacım olan aynı sorunu yaşadım. Benim durumumda React Router v3 kullanıyordum , bu yüzden kullanamadım <Prompt />, React Router v4'ten tanıtılan .

'Geri düğmesi tıklamasını' ve 'yanlışlıkla bağlantı tıklamasını' ve kombinasyonuyla setRouteLeaveHookve olay işleyiciyle history.pushState()'yeniden yükle düğmesini' onbeforeunloadişledim.

setRouteLeaveHook ( doc ) & history.pushState ( doc )

  • Yalnızca setRouteLeaveHook kullanmak yeterli değildi. Bazı nedenlerden dolayı, 'geri düğmesi' tıklandığında sayfa aynı kalmasına rağmen URL değiştirildi.

      // 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 ( belge )

  • 'Yanlışlıkla yeniden yükleme' düğmesini işlemek için kullanılır

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

Aşağıda yazdığım tam bileşen

  • withRouter'ın sahip olmak için kullanıldığını unutmayın this.props.router.
  • this.props.routeçağıran bileşenden aktarılan not
  • currentStateilk duruma sahip olmak ve herhangi bir değişikliği kontrol etmek için prop olarak iletildiğine dikkat edin

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

Herhangi bir sorunuz varsa lütfen bana bildirin :)


this.formStartEle tam olarak nereden geliyor?
Gaurav

2

For react-routerile v0.13.x reactv0.13.x:

bu willTransitionTo()ve willTransitionFrom()statik yöntemlerle mümkündür . Daha yeni sürümler için aşağıdaki diğer cevabıma bakın.

Kaynaktan reaksiyona-yönlendirici belgeler :

Rota işleyicilerinizde, rota geçişleri sırasında çağrılacak bazı statik yöntemler tanımlayabilirsiniz.

willTransitionTo(transition, params, query, callback)

Bir işleyici oluşturmak üzereyken çağrılır ve size geçişi durdurma veya yeniden yönlendirme fırsatı verir. Bazı eşzamansız işler yaparken geçişi duraklatabilir ve işiniz bittiğinde geri aramayı (hata) arayabilir veya bağımsız değişken listenizdeki geri aramayı atlayabilirsiniz ve sizin için çağrılacaktır.

willTransitionFrom(transition, component, callback)

Etkin bir rotadan geçiş yapılırken çağrılır ve size geçişi iptal etme fırsatı verir. Bileşen mevcut bileşendir, geçişe izin vermek isteyip istemediğinize karar vermek için muhtemelen durumunu kontrol etmeniz gerekecektir (form alanları gibi).

Misal

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

    //...
  });

İçin react-router1.0.0-RC1 reactsonra v0.14.x veya:

bu, routerWillLeaveyaşam döngüsü kancası ile mümkün olmalıdır . Daha eski sürümler için yukarıdaki cevabıma bakın.

Kaynaktan reaksiyona-yönlendirici belgeler :

Bu kancayı takmak için, tesisat bileşenlerinizden birinde Yaşam Döngüsü karışımını kullanı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?'
    },

    // ...

  })

Bir şeyler. ancak son sürümden önce değişebilir.


1

Bu istemi kullanabilirsiniz.

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 kullanarak

Örneğin aşağıdaki gibi:

Bileşeninizde,

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

5
Merhaba, Stack Overflow'a hoş geldiniz. Zaten birçok cevabı olan bir soruyu cevaplarken, lütfen verdiğiniz cevabın neden esaslı olduğuna ve orijinal gönderen tarafından halihazırda incelenmiş olanı tekrarlamadığına dair bazı ek bilgiler eklediğinizden emin olun. Bu, sağladığınız gibi "yalnızca kod" yanıtlarında özellikle önemlidir.
chb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.