A continuación se muestran las versiones del paquete que estoy usando.
React version - 16.13.1 react-router-dom version - 6.0.0-beta.0 react-redux version 7.2.0 Material UI version 4.11.0 ¿Cómo/cuál es la mejor manera de comprobar que un formulario isDirty (ha cambiado) cuando el usuario intenta salir de la página actual? Me gustaría preguntar "¿Está seguro de que desea irse..." si el formulario isDirty .
Obtendré los datos desde useEffect() y usaré un reductor de redux para representar la interfaz de usuario.
¿Debo declarar una variable para mantener los datos obtenidos originales para la verificación sucia?
Esto es lo que estoy haciendo, pero no funciona correctamente.
componente.js
useEffect(() => { props.fetchUserInfo(); })acción.js
export function fetchUserInfo() { return (dispatch) => { dispatch({type: USER_INITIALSTATE, {Name: 'abc', Age: 20}} ) } }usuarioReductor.js
const initialState = { processing: false, success: false, fail: false, Profile: {} } let oriState; let State; const UserReducer = (state = initialState, action) => { if (action.type === USER_INITIALSTATE) { oriState = {Profile: action.data}; State = {...state, Profile: action.data}; return {...state, Profile: action.data}; } else if (action.type === OTHERS_ACTION) { //update field change return {...state, xxx} } } export const userIsDirty = state => { if (oriState && State) { return JSON.stringify(oriState.Profile) !== JSON.stringify(State.Profile); } return false; }; export default UserReducer; Entonces, en mi componente, llamo a userIsDirty para devolver el booleano isDirty, pero no he descubierto cómo capturar el evento de página de salida y usar esto como un disparador para hacer la verificación del formulario sucio.
Entonces, ¿cómo detectar salir de la página actual? Intenté algo en useEffect return(componente umount), pero los accesorios no obtienen el estado INITIALSTATE actualizado (lo que significa que obtendré Profile: {}), porque solo se ejecuta una vez, pero si agrego el argumento de matriz opcional useEffect, obtengo un bucle infinito (¿tal vez lo configuré mal?).
useEffect(() => { props.fetchUserInfo(); return () => { console.log(props); //not getting initial state object }; }, []);¿Estoy haciendo esto de la manera correcta? ¿Qué me he perdido? ¿Hay una solución mejor/correcta para lograr lo que quiero?
Gracias @gdh, useBlocker es el que quiero. Lo estoy usando para abrir un cuadro de diálogo de confirmación.
Compartiré mis códigos y caja completos, creo que esto puede ser útil para alguien en el futuro.
Los ganchos que @gdh mencionó en su respuesta fueron eliminados por el equipo de desarrolladores de react-router. Por eso, no puede usar usePrompt o useBlocker con la versión actual de react-router (v6).
Pero el equipo mencionó que están trabajando intensamente en las funciones. referencia
Si alguien quiere implementar los cambios que hizo el equipo de remixes para ofrecer las funcionalidades de los ganchos, puede echar un vistazo a esta respuesta de github. aquí
Me enfrentaba a la misma situación de intentar utilizar un cuadro de diálogo de confirmación de IU "agradable" personalizado que se integraba con el gancho useBlocker de react router v6 beta para bloquear las transiciones de ruta cuando el formulario de la ruta actual tiene modificaciones no guardadas. Comencé con el código de codesandbox vinculado en la sección UPDATED al final de esta pregunta. Encontré que esta implementación de enlace personalizado no funcionaba para todas mis necesidades, así que la adapté para admitir un parámetro de expresión regular opcional para definir un conjunto de rutas que no deberían bloquearse. También cabe destacar que la implementación de codesandbox devuelve un booleano de la devolución de llamada pasada a useBlocker , pero descubrí que esto no tiene ningún efecto ni utilidad, así que lo eliminé. Aquí está mi implementación completa de TypeScript de un gancho personalizado revisado:
useNavigationWarning.ts
import { useState, useEffect, useCallback } from 'react'; import { useBlocker, useNavigate, useLocation } from 'react-router-dom'; import { Blocker } from 'history'; export function useNavigationWarning( when: boolean, exceptPathsMatching?: RegExp ) { const navigate = useNavigate(); const location = useLocation(); const [showPrompt, setShowPrompt] = useState<boolean>(false); const [lastLocation, setLastLocation] = useState<any>(null); const [confirmedNavigation, setConfirmedNavigation] = useState<boolean>( false ); const cancelNavigation = useCallback(() => { setShowPrompt(false); }, []); const handleBlockedNavigation = useCallback<Blocker>( nextLocation => { const shouldIgnorePathChange = exceptPathsMatching?.test( nextLocation.location.pathname ); if ( !(confirmedNavigation || shouldIgnorePathChange) && nextLocation.location.pathname !== location.pathname ) { setShowPrompt(true); setLastLocation(nextLocation); } else if (shouldIgnorePathChange) { // to cancel blocking based on the route we need to retry the nextLocation nextLocation.retry(); } }, [confirmedNavigation, location.pathname, exceptPathsMatching] ); const confirmNavigation = useCallback(() => { setShowPrompt(false); setConfirmedNavigation(true); }, []); useEffect(() => { if (confirmedNavigation && lastLocation?.location) { navigate(lastLocation.location.pathname); // Reset hook state setConfirmedNavigation(false); setLastLocation(null); } }, [confirmedNavigation, lastLocation, navigate]); useBlocker(handleBlockedNavigation, when); return [showPrompt, confirmNavigation, cancelNavigation] as const; }@Devb, su pregunta y actualización fueron muy útiles y me ahorraron mucho tiempo. ¡Gracias! creó un HOC basado en su código. podría ser útil para alguien. accesorios en el componente envuelto:
setPreventNavigation: establece cuándo bloquear la navegación
provideLeaveHandler: establece la función que se ejecutará cuando intente cambiar una ruta y esté bloqueado para la navegación
confirmNavigation - continuar la navegación
cancelNavigation - detener la navegación
import React, { useEffect, useState, useCallback } from 'react' import { useNavigate, useBlocker, useLocation } from 'react-router-dom' export default function withPreventNavigation(WrappedComponent) { return function preventNavigation(props) { const navigate = useNavigate() const location = useLocation() const [lastLocation, setLastLocation] = useState(null) const [confirmedNavigation, setConfirmedNavigation] = useState(false) const [shouldBlock, setShouldBlock] = useState(false) let handleLeave = null const cancelNavigation = useCallback(() => { setshouldBlock(false) },[]) const handleBlockedNavigation = useCallback( nextLocation => { if ( !confirmedNavigation && nextLocation.location.pathname !== location.pathname ) { handleLeave(nextLocation) setLastLocation(nextLocation) return false } return true }, [confirmedNavigation] ) const confirmNavigation = useCallback(() => { setConfirmedNavigation(true) }, []) useEffect(() => { if (confirmedNavigation && lastLocation) { navigate(lastLocation.location.pathname) } }, [confirmedNavigation, lastLocation]) const provideLeaveHandler = handler => { handleLeave = handler } useBlocker(handleBlockedNavigation, shouldBlock) return ( <WrappedComponent {...props} provideLeaveHandler={provideLeaveHandler} setPreventNavigation={setShouldBlock} confirmNavigation={confirmNavigation} cancelNavigation={cancelNavigation} /> ) } }Parece que está buscando el evento beforeunload .
Lea atentamente ya que no todos los navegadores cumplen con event.preventDefault() .
En el controlador de eventos, puede hacer las comprobaciones que desee y evitar que la ventana se cierre según sus requisitos.
Espero que esto ayude.
Esta respuesta usa el enrutador v6.
usePrompt("Hello from usePrompt -- Are you sure you want to leave?", isBlocking); useBlocker( () => "Hello from useBlocker -- are you sure you want to leave?", isBlocking );pop-up/modal box interfaz de usuario personalizado en lugar del default prompt del navegador y está usando react-router (v4) con history . Puede hacer uso del custom history y configurar su router como
import createBrowserHistory from 'history/createBrowserHistory' export const history = createBrowserHistory() ... import { history } from 'path/to/history'; <Router history={history}> <App/> </Router> y luego en su componente de aviso personalizado puede hacer uso de history.block como
import { history } from 'path/to/history'; class MyCustomPrompt extends React.Component { componentDidMount() { this.unblock = history.block(targetLocation => { // take your action here return false; }); } componentWillUnmount() { this.unblock(); } render() { //component render here } } Agregue este MyCustomPrompt en sus componentes donde quiera bloquear la navegación.