Виявлення вихідної сторінки користувача за допомогою реактора-маршрутизатора


116

Я хочу, щоб моя програма ReactJS сповіщала користувача під час переходу від певної сторінки. Зокрема спливаюче повідомлення, яке нагадує йому зробити дію:

"Зміни збережені, але ще не опубліковані. Робити це зараз?"

Чи потрібно це запустити на react-routerглобальному рівні, чи це щось можна зробити з сторінки / компонента реагування?

Я не знайшов нічого на останньому, і я вважаю за краще уникати першого. Якщо це, звичайно, не є нормою, але це змушує мене замислитись, як зробити таку річ без необхідності додавати код на будь-яку іншу можливу сторінку, на яку може перейти користувач ..

Будь-яка інформація просимо, дякую!


3
Я зараз не знаю, якщо це те, що ви шукаєте, але ви можете зробити що-небудь. як це, 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 } }тож ви можете зателефонувати на функцію публікації замість того, щоб залишити сторінку
Rico Berger

Відповіді:


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

Чи є спосіб відобразити той же компонент підказки, як тільки користувач натисне на кнопку CANCEL, наприклад?
Рене Енрікес

1
@ReneEnriquez react-routerне підтримує це поза полем (якщо припустити, що кнопка "Скасувати" не викликає жодних змін маршруту). Ви можете створити власний модал, щоб імітувати поведінку.
jcady

6
Якщо ви закінчите користуватися onbeforeunload , вам потрібно буде очистити його, коли ваш компонент відключений. componentWillUnmount() { window.onbeforeunload = null; }
cdeutsch

40

У реакторі-маршрутизаторі 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використання підказки або спеціальної історії:

Однак у react-router v4, його досить простіше реалізувати за допомогою Promptмаршрутизатора

Відповідно до документації

Підкажіть

Використовується для сповіщення користувача перед відходом від сторінки. Коли ваша програма перейде в стан, який повинен перешкоджати користувачеві відходити (наприклад, форма заповнена наполовину), зробіть a <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}?`
)}/>

коли: бул

Замість того, щоб умовно виводити <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-адресою, вона буде змінена, якщо натиснути Скасувати в запиті Пов’язаний випуск: github.com/ReactTraining/react-router/isissue/5405
Сахан Серасінгхе

1
Я бачу, це питання все ще досить популярне. Оскільки найвищі відповіді стосуються старих застарілих версій, я ставлю цю більш нову відповідь як прийняту. Посилання на стару документацію (та посібники з оновлення) тепер доступні на веб-
Barry Staes

1
onLeave запускається ПІСЛЯ підтвердження зміни маршруту. Чи можете ви детальніше розповісти про те, як скасувати навігацію в OnLeave?
шлінгел

23

Для react-router2.4.0+

ПРИМІТКА . Бажано перенести весь код до останнього, 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

Як @Learner: Як це зробити за допомогою спеціального вікна підтвердження, наприклад vex, SweetAlert чи іншого діалогового блокування?
олефранк

У мене є така помилка: - TypeError: Неможливо прочитати властивість ' id ' невизначеного. Будь-яка пропозиція
Мустафа Мамун

@MustafaMamun Це, здається, не пов'язане між собою, і ви, головним чином, захочете створити нове запитання з поясненням деталей.
snymkpr

У мене виникла проблема під час спроби використовувати рішення. Компонент повинен бути безпосередньо підключений до маршрутизатора, тоді це рішення працює. Я знайшов кращу відповідь там stackoverflow.com/questions/39103684/…
Мустафа Мамун

4

Для react-routerv3.x

У мене був той самий випуск, де мені було потрібно повідомлення про підтвердження будь-яких незбережених змін на сторінці. У моєму випадку я використовував React Router v3 , тому я не міг його використовувати <Prompt />, що було введено з React Router v4 .

Я обробляв "натискання кнопки назад" та "випадкове натискання посилання" з комбінацією 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 ( doc )

  • Він використовується для обробки кнопки "випадкове перезавантаження"

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

Нижче наведено повний компонент, який я написав

  • зауважте, що раніше використовується RRUTERthis.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);

Будь ласка, дайте мені знати, чи є питання :)


звідки саме це.formStartEle походить?
Гаурав

2

Для react-routerv0.13.x з reactv0.13.x:

це можливо за допомогою willTransitionTo()і willTransitionFrom()статичних методів. Щодо новіших версій, дивіться іншу мою відповідь нижче.

З документації про маршрутизатор :

Ви можете визначити деякі статичні методи для обробників маршрутів, які будуть викликатися під час переходу маршруту.

willTransitionTo(transition, params, query, callback)

Викликається, коли обробник збирається надати, надаючи вам можливість перервати або перенаправити перехід. Ви можете призупинити перехід, виконуючи якусь асинхонічну роботу та зворотній виклик дзвінка (помилка), коли закінчите, або пропустіть зворотний виклик у списку аргументів, і він буде закликаний для вас.

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- reactrc1 з v0.14.x або новішою версією:

це має бути можливо за допомогою routerWillLeaveгачка життєвого циклу. Старіші версії див. Мою відповідь вище.

З документації про маршрутизатор :

Щоб встановити цей гачок, використовуйте змішувач Lifecycle в одному з компонентів маршруту.

  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. Відповідаючи на запитання, на яке вже є багато відповідей, будь ласка, не забудьте додати деяку додаткову інформацію про те, чому відповідь, яку ви надаєте, є суттєвою, а не просто повторює те, що вже було перевірено оригінальним плакатом. Це особливо важливо у відповідях "лише для коду", таких як той, який ви надали.
чб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.