Below are the package versions I'm using.
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
How/what is the best way to check that a form isDirty
(has changed) when the user is trying to leave the current page? I would like to prompt "Are you sure want to leave...." if the form isDirty
.
I will fetch the data from within useEffect()
and use a redux reducer to render the UI.
Should I declare a variable to keep the original fetched data for dirty checking?
This is what I am doing, but it is not working correctly.
component.js
useEffect(() => {
props.fetchUserInfo();
})
action.js
export function fetchUserInfo() {
return (dispatch) => {
dispatch({type: USER_INITIALSTATE, {Name: 'abc', Age: 20}}
)
}
}
userReducer.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;
So in my component I call userIsDirty
to return the isDirty boolean, but I haven't figured out how to catch the leave page event and use this as a trigger to do the dirty form checking.
So how to detect leaving the current page? I tried something on useEffect return(component umount), but the props is not getting the updated INITIALSTATE state (meaning I will get Profile: {}), because it only runs once, but if I add the useEffect optional array argument, I get an infinite loop(maybe I set it wrong?).
useEffect(() => {
props.fetchUserInfo();
return () => {
console.log(props); //not getting initial state object
};
}, []);
Am I doing this the correct way? What have I missed? Is there a better/correct solution to achieve what I want?
Thanks @gdh, useBlocker
is the one I want. I am using it to popup a confirmation dialog.
I will share my complete codesandbox, I believe this may be helpful for someone in the future.
show confirmation dialog by using useBlocker
To detect user leaving page with React Router, we can use the Prompt component. import { Prompt } from "react-router"; const MyComponent = () => ( <> <Prompt when={shouldBlockNavigation} message="Are you sure you want to leave the page?" /> {/* Component JSX */} </> );
Use your router and pass your history object to it. In a component you want to listen to location changes on, import your history object and invoke the listen callback as you did previously. import history from '../myHistory'; ...
Note that this example makes use of the withRouter higher-order component introduced in v2. 4.0. Used to prompt the user before navigating away from a page. When your application enters a state that should prevent the user from navigating away (like a form is half-filled out), render a <Prompt> .
To detect route change with React Router, we can use the useLocation hook. import { useEffect } from "react"; import { useLocation } from "react-router-dom"; const SomeComponent = () => { const location = useLocation(); useEffect(() => { console. log("Location changed"); }, [location]); //... };
Just adding an additional answer for React Router v6 users.
As of v6.0.0-beta - useBlocker and usePrompt were removed (to be added back in at a later date).
It was suggsested if we need them in v6.0.2 (current version at the time of writing) that we should use existing code as an example.
Here is the code directly from the the alpha for these hooks.
So to add the hooks back in would be this code (anywhere in your app for usage):
** I only copied the code for react-router-dom - if you're using native, then you'll need to check the above link for the other usePrompt
hook
/**
* These hooks re-implement the now removed useBlocker and usePrompt hooks in 'react-router-dom'.
* Thanks for the idea @piecyk https://github.com/remix-run/react-router/issues/8139#issuecomment-953816315
* Source: https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381
*/
import { useContext, useEffect, useCallback } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
/**
* Blocks all navigation attempts. This is useful for preventing the page from
* changing until some condition is met, like saving form data.
*
* @param blocker
* @param when
* @see https://reactrouter.com/api/useBlocker
*/
export function useBlocker( blocker, when = true ) {
const { navigator } = useContext( NavigationContext );
useEffect( () => {
if ( ! when ) return;
const unblock = navigator.block( ( tx ) => {
const autoUnblockingTx = {
...tx,
retry() {
// Automatically unblock the transition so it can play all the way
// through before retrying it. TODO: Figure out how to re-enable
// this block if the transition is cancelled for some reason.
unblock();
tx.retry();
},
};
blocker( autoUnblockingTx );
} );
return unblock;
}, [ navigator, blocker, when ] );
}
/**
* Prompts the user with an Alert before they leave the current screen.
*
* @param message
* @param when
*/
export function usePrompt( message, when = true ) {
const blocker = useCallback(
( tx ) => {
// eslint-disable-next-line no-alert
if ( window.confirm( message ) ) tx.retry();
},
[ message ]
);
useBlocker( blocker, when );
}
Then the usage would be:
const MyComponent = () => {
const formIsDirty = true; // Condition to trigger the prompt.
usePrompt( 'Leave screen?', formIsDirty );
return (
<div>Hello world</div>
);
};
Prompt, usePrompt and useBlocker have been removed from react-router-dom. This answer will not currently work, though this might change. The github issue, opened Oct 2021, is here
This answer uses router 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
);
Demo for both 1 & 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With