diff --git a/packages/ra-core/src/sideEffect/admin.js b/packages/ra-core/src/sideEffect/admin.js index 3ed89b2763c..b600301b6dc 100644 --- a/packages/ra-core/src/sideEffect/admin.js +++ b/packages/ra-core/src/sideEffect/admin.js @@ -9,6 +9,7 @@ import redirection from './redirection'; import accumulate from './accumulate'; import refresh from './refresh'; import undo from './undo'; +import unload from './unload'; /** * @param {Object} dataProvider A Data Provider function @@ -26,5 +27,6 @@ export default (dataProvider, authProvider, i18nProvider) => refresh(), notification(), callback(), + unload(), ]); }; diff --git a/packages/ra-core/src/sideEffect/index.js b/packages/ra-core/src/sideEffect/index.js index 876c36e6956..0cf011c3c39 100644 --- a/packages/ra-core/src/sideEffect/index.js +++ b/packages/ra-core/src/sideEffect/index.js @@ -9,6 +9,7 @@ import accumulateSaga from './accumulate'; import refreshSaga from './refresh'; import i18nSaga from './i18n'; import undoSaga from './undo'; +import unloadSaga from './unload'; export { adminSaga, @@ -22,4 +23,5 @@ export { refreshSaga, i18nSaga, undoSaga, + unloadSaga, }; diff --git a/packages/ra-core/src/sideEffect/unload.ts b/packages/ra-core/src/sideEffect/unload.ts new file mode 100644 index 00000000000..db7e2e6412a --- /dev/null +++ b/packages/ra-core/src/sideEffect/unload.ts @@ -0,0 +1,44 @@ +import { takeEvery } from 'redux-saga/effects'; + +import { + START_OPTIMISTIC_MODE, + STOP_OPTIMISTIC_MODE, +} from '../actions/undoActions'; + +/** + * Unload saga + * + * When a user closes the browser window while an optimistic update/delete + * hasn't been sent to the dataProvider yet, warn them that their edits + * may be lost. + * + * To achieve that, this saga registers a window event handler on the + * 'beforeunload' event when entering the optimistic mode, and removes + * the event when quitting the optimistic mode. + */ +export default function* watchUnload() { + yield takeEvery(START_OPTIMISTIC_MODE, handleStartOptimistic); + yield takeEvery(STOP_OPTIMISTIC_MODE, handleStopOptimisticMode); +} + +const eventListener = event => { + event.preventDefault(); // standard + event.returnValue = ''; // Chrome + return 'Your latest modifications are not yet sent to the server. Are you sure?'; // Old IE +}; + +export function* handleStartOptimistic() { + // SSR escape hatch + if (!window) { + return; + } + window.addEventListener('beforeunload', eventListener); +} + +export function* handleStopOptimisticMode() { + // SSR escape hatch + if (!window) { + return; + } + window.removeEventListener('beforeunload', eventListener); +}