Detekce obnovení stránky, zavření karty a změny směrování pomocí React Router v5

Představte si, že po vyplnění povinného a nudného dotazníku omylem zavřete kartu prohlížeče. Všechny vaše odpovědi jsou nyní ztraceny.

Frustrující, že?

Takový zážitek byste svým uživatelům nechtěli poskytnout. Zde je návod, jak to můžete napravit.

Problém:

Jak upozornit uživatele, když náhodou...

  1. Znovu načtěte stránku.
  2. Zavřete kartu nebo okno prohlížeče.
  3. Stiskněte tlačítko Zpět v prohlížeči.
  4. Klikněte na odkaz/změňte trasu.

Řešení:

Část 1. Detekce opětovného načtení stránky a zavření karty prohlížeče

Zavření karty/okna nebo událost opětovného načtení stránky znamenají, že aktuální dokument a jeho zdroje budou odstraněny (uvolněny). V tomto případě beforeunload událost je spuštěna.

V bodě, ve kterém beforeunload událost je spuštěna, dokument je stále viditelný a událost lze zrušit, což znamená unload události lze zabránit, jako by se nikdy nestala.

Tato událost umožňuje webové stránce spustit potvrzovací dialog s dotazem uživatele, zda skutečně chce stránku opustit. Pokud uživatel potvrdí, prohlížeč přejde na novou stránku, v opačném případě navigaci zruší.

Zabránění beforeunload událost

window.onbeforeunload = (event) => {
  const e = event || window.event;
  // Cancel the event
  e.preventDefault();
  if (e) {
    e.returnValue = ''; // Legacy method for cross browser support
  }
  return ''; // Legacy method for cross browser support
};

Všechny 3 metody výše e.preventDefault() , e.returnValue = '' a return '' zabránit provedení události.

Příklad zobrazeného potvrzovacího pole:

Poznámka: Bohužel přizpůsobená zpráva není podporována ve všech prohlížečích

Zobrazit výzvu na základě stavu

#1 Vytvořte funkci se stavem React showExitPrompt jako parametr a inicializujte onbeforeunload posluchač uvnitř funkce. Použijte stav uvnitř posluchače událostí.

Proč předávat stav React jako parametr?
Protože onbeforeunload je vanilkový javascriptový posluchač událostí a jakákoli změna stavu React neaktualizuje stav uvnitř jeho zpětného volání.

import { useState } from 'react';

const initBeforeUnLoad = (showExitPrompt) => {
  window.onbeforeunload = (event) => {
    // Show prompt based on state
    if (showExitPrompt) {
      const e = event || window.event;
      e.preventDefault();
      if (e) {
        e.returnValue = ''
      }
      return '';
    }
  };
};

#2 Vytvořte stav showExitPrompt ke správě výzvy a registraci posluchače událostí při načítání stránky.

function MyComponent() {
  const [showExitPrompt, setShowExitPrompt] = useState(false);

  // Initialize the beforeunload event listener after the resources are loaded
  window.onload = function() {
    initBeforeUnLoad(showExitPrompt);
  };
}

#3 Znovu inicializujte posluchač události při změně stavu.

import { useState, useEffect } from 'react';

const initBeforeUnLoad = (showExitPrompt) => {
  // … code
}

function MyComponent() {
  const [showExitPrompt, setShowExitPrompt] = useState(false);

  window.onload = function() {
    initBeforeUnLoad(showExitPrompt);
  };

  // Re-Initialize the onbeforeunload event listener
  useEffect(() => {
    initBeforeUnLoad(showExitPrompt);
  }, [showExitPrompt]);
}

Nyní jste připraveni jej použít uvnitř vaší komponenty. ALE je efektivní vytvořit vlastní hák pro nastavení a přístup ke stavu kdekoli v aplikaci.

Použijte vlastní háček

#1 Soubor háčku useExitPrompt.js

import { useState, useEffect } from 'react';

const initBeforeUnLoad = (showExitPrompt) => {
  window.onbeforeunload = (event) => {
    if (showExitPrompt) {
      const e = event || window.event;
      e.preventDefault();
      if (e) {
        e.returnValue = '';
      }
      return '';
    }
  };
};

// Hook
export default function useExitPrompt(bool) {
  const [showExitPrompt, setShowExitPrompt] = useState(bool);

  window.onload = function() {
    initBeforeUnLoad(showExitPrompt);
  };

  useEffect(() => {
    initBeforeUnLoad(showExitPrompt);
  }, [showExitPrompt]);

  return [showExitPrompt, setShowExitPrompt];
}

#2 Komponentní soubor MyComponent.js
Poznámka: Budete muset resetovat hodnotu showExitPrompt stav na výchozí, když je komponenta odpojena.

import useExitPrompt from './useExitPrompt.js'

export default function MyComponent() {
  const [showExitPrompt, setShowExitPrompt] = useExitPrompt(false);

  const handleClick = (e) => {
    e.preventDefault();
    setShowExitPrompt(!showExitPrompt)
  }

  //NOTE: this similar to componentWillUnmount()
  useEffect(() => {
    return () => {
      setShowExitPrompt(false)
    }
  }, [])

  return (
    <div className="App">
      <form>{/*Your code*/}</form>
      <button onClick={handleClick}>Show/Hide the prompt</button>
      <Child setShowExitPrompt={setShowExitPrompt} />
    </div>
  );
}

NEBO

#2 Komponentní soubor App.js
Předejte to svým podřízeným komponentám prostřednictvím Context.Provider a k hodnotě přistupujte pomocí useContext() háček kdekoli ve vaší aplikaci.

import useExitPrompt from './useExitPrompt.js'
import MyContext from './MyContext.js'

export default function App() {
  const [showExitPrompt, setShowExitPrompt] = useExitPrompt(false);

  return (
    <div className="App">
      <MyContext.Provider value={{showExitPrompt, setShowExitPrompt}}>
        <MyMainApp />
      </MyContext.Provider>
    </div>
  );
}

export default function MyComponent() {
  const { showExitPrompt, setShowExitPrompt } = useContext(MyContext);

  //NOTE: this works similar to componentWillUnmount()
  useEffect(() => {
    return () => {
      setShowExitPrompt(false);
    }
  }, [])

  return (
    <div>{/* your code */}</div>
  );
}

Část 2. Detekce změny trasy/stránky a zpět v prohlížeči

Podobně jako u výše uvedených akcí, když uživatel klikne na odkaz, je přesměrován na novou stránku a dokument a jeho zdroje budou uvolněny.

React Router však funguje jinak, implementuje rozhraní History API, které poskytuje přístup k historii relace prohlížeče. Kliknutím na běžný odkaz přejdete na novou adresu URL a nový dokument (stránku), mezitím history umožňuje „falešnou“ adresu URL, aniž byste opustili stránku.

location.pathname vs history.pushState()

window.location.pathname = '/dummy-page'

V/S

window.history.pushState({}, '', '/dummy-page')

Vidíte ten rozdíl? history.pushState() nezmění pouze URL nic jiného, ​​celá stránka zůstane nedotčena, zatímco location.pathname přesměruje vás na tuto novou stránku, pravděpodobně zobrazí chybu 404, protože taková trasa neexistuje.

Zobrazení výzvy s getUserConfirmation() a <Prompt/> komponent

React Router poskytuje podporu getUserConfirmation() v <BrowserRouter> pro potvrzení navigace a komponenty <Prompt/> zobrazíte vlastní zprávu z vašich podřízených komponent.

#1 Kořenový soubor App.js

import { BrowserRouter } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter getUserConfirmation={(message, callback) => {
      // this is the default behavior
      const allowTransition = window.confirm(message);
      callback(allowTransition);
      }}
    >
      <Routes />
    </BrowserRouter>
  );
}

window.confirm() zobrazí zprávu, kterou předáte, v React Routeru <Prompt /> komponent z vašich příslušných dětských komponent. callback() funkce vyžaduje booleovský parametr, který zabrání přechodu na novou stránku.

#2 Komponentní soubor MyForm.js
<Prompt /> má 2 rekvizity, when a message . Pokud when hodnota prop je nastavena na true a uživatel klikne na jiný odkaz, zobrazí se mu zpráva předaná v message rekvizity.

import { Prompt } from 'react-router-dom';

function MyForm() {
  const [isFormIncomplete, setIsFormIncomplete] = useState(true);
  return (
    <div>
     <form>{/*Your code*/}</form>

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

Příklad zobrazeného potvrzovacího pole:

Souhrn

Pokud akce uživatele...

  1. Odstraní zdroje stránky, použijte beforeunload událost vanilla JavaScript, která uživatele vyzve.
  2. Změňte pouze zobrazení, použijte getUserConfirmation() v <BrowserRouter/> spolu s <Prompt /> komponenta k zobrazení výzvy uživatele.