diff --git a/react/hooks/useConfirmExit.js b/react/hooks/useConfirmExit.js
deleted file mode 100644
index 9d03eaad4f..0000000000
--- a/react/hooks/useConfirmExit.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { useCallback, useRef } from 'react'
-import useEventListener from './useEventListener'
-
-/**
- * When provided a message, will warn the user before exiting the page
- *
- * Warning: The confirmation message is required but will usually
- * be replaced by an internal message by the browser, and never displayed.
- *
- * A falsy error message would deactivate to confirmation popup.
- *
- * @param {string|null} message - Confirmation message
- * @param {function} callback - will be executed before returning
- */
-export default function useConfirmExit(message, callback) {
-  // Using a ref in order to have an event listener that does not
-  // need to be deregistered, recreated and registered again at each
-  // message or callback change. If not, the lag introduced by the
-  // useEffect inside useEventListener may create wrong behaviours
-  // for fast changing calls to useConfirmExit.
-  const state = useRef()
-  state.current = { message, callback }
-  const beforeunload = useCallback(event => {
-    state.current.callback && state.current.callback()
-    const returnValue = state.current.message || null
-    if (returnValue) event.returnValue = returnValue
-    return returnValue
-  }, [])
-  useEventListener(window, 'beforeunload', beforeunload)
-}
diff --git a/react/hooks/useConfirmExit.spec.js b/react/hooks/useConfirmExit.spec.js
deleted file mode 100644
index 7a2770939d..0000000000
--- a/react/hooks/useConfirmExit.spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import useConfirmExit from './useConfirmExit'
-import { renderHook } from '@testing-library/react-hooks'
-
-const triggerBeforeUnload = () => {
-  const event = new Event('beforeunload')
-  return window.dispatchEvent(event)
-}
-
-describe('useConfirmExit', () => {
-  it('should subscribe to window.onbeforeunload at mount', async () => {
-    const cb = jest.fn()
-    renderHook(() => useConfirmExit('message', cb))
-
-    triggerBeforeUnload()
-    expect(cb).toHaveBeenCalledTimes(1)
-  })
-
-  it('should unsubscribe to window.onbeforeunload at dismount', async () => {
-    const cb = jest.fn()
-    const { unmount } = renderHook(() => useConfirmExit('message', cb))
-    unmount()
-
-    triggerBeforeUnload()
-    expect(cb).toHaveBeenCalledTimes(0)
-  })
-
-  it('should allow a null message', async () => {
-    const cb = jest.fn()
-    renderHook(() => useConfirmExit(null, cb))
-
-    triggerBeforeUnload()
-    expect(cb).toHaveBeenCalledTimes(1)
-  })
-
-  it('should allow a null callback', async () => {
-    renderHook(() => useConfirmExit('message'))
-
-    triggerBeforeUnload()
-  })
-})
diff --git a/react/hooks/useConfirmExit/index.js b/react/hooks/useConfirmExit/index.js
new file mode 100644
index 0000000000..5fcc4fa3bc
--- /dev/null
+++ b/react/hooks/useConfirmExit/index.js
@@ -0,0 +1,151 @@
+import React, { useCallback, useRef, useState } from 'react'
+import PropTypes from 'prop-types'
+
+import useEventListener from '../useEventListener'
+import Modal from '../../Modal'
+import withLocales from '../../I18n/withLocales'
+
+import en from './locales/en.json'
+import fr from './locales/fr.json'
+
+/**
+ * Confirmation modal
+ * @private
+ * @param {string} message - Confirmation message
+ * @param {string} title - Title of the modal
+ * @param {function} onConfirm - will be executed on confirmation
+ * @param {function} onCancel - will be executed on cancelation
+ */
+function ConfirmModal({ t, title, message, onConfirm, onCancel }) {
+  return (
+    <Modal
+      closable={false}
+      mobileFullscreen={false}
+      primaryAction={onConfirm}
+      primaryType="regular"
+      primaryText={t('useConfirmExit.leave')}
+      secondaryAction={onCancel}
+      secondaryType="secondary"
+      secondaryText={t('useConfirmExit.back')}
+      description={message || t('useConfirmExit.message')}
+      title={title || t('useConfirmExit.title')}
+    />
+  )
+}
+ConfirmModal.PropTypes = {
+  message: PropTypes.string,
+  title: PropTypes.string,
+  onConfirm: PropTypes.func.isRequired,
+  onCancel: PropTypes.func.isRequired
+}
+const dictRequire = { en, fr }
+const LocalizedConfirmModal = withLocales(dictRequire)(ConfirmModal)
+
+/**
+ * Go to the requested destination (URL or function)
+ *
+ * @param {string|function} destination
+ */
+function go(destination) {
+  if (typeof destination === 'function') {
+    destination()
+  } else if (typeof destination === 'string') {
+    document.location = destination
+  } else {
+    throw new Error(`Unknown location where to jump to`)
+  }
+}
+
+function isActivated(activate) {
+  return typeof activate === 'function' ? activate() : activate
+}
+
+/**
+ * @typedef useConfirmExitResponse
+ * @property {function} requestToLeave - gets an URL or function,
+ * triggers a confirmation modal and redirect the browser to this URL
+ * or call this function if the user confirms.
+ * @property {function} exitConfirmationModal - React component
+ * that will show a confirmation modal when requested by requestToLeave
+ * and nothing otherwise
+ */
+
+/**
+ * When provided a message, will warn the user before exiting the page
+ *
+ * When the browser detects a page unload (go to another website or
+ * leave the window/tab), it will show a native popup asking for
+ * confirmation. This popup may show the `message` but will usually
+ * use a native message from the browser.
+ *
+ * If the user confirm he wants to leave, `onLeave` will be executed.
+ * This function may not be able to execute async code.
+ *
+ * @param {bool|function} activate - (return) falsy to deactivate the behaviour
+ * @param {string} message - Confirmation message
+ * @param {string} title - Title of the modal
+ * @param {function} onLeave - will be executed before returning
+ * @returns {useConfirmExitResponse}
+ */
+export default function useConfirmExit({
+  activate = true,
+  onLeave,
+  message,
+  title
+}) {
+  // `onbeforeunload` event on the browser:
+  // Using a ref in order to have an event listener that does not
+  // need to be deregistered, recreated and registered again at each
+  // message or callback change. If not, the lag introduced by the
+  // useEffect inside useEventListener may create wrong behaviours
+  // for fast changing calls to useConfirmExit.
+  const state = useRef()
+  state.current = { message, onLeave, activate }
+  const beforeunload = useCallback(event => {
+    const activated = isActivated(state.current.activate)
+    activated && state.current.onLeave && state.current.onLeave()
+    const returnValue = activated ? state.current.message : null
+    if (returnValue) event.returnValue = returnValue
+    return returnValue
+  }, [])
+  useEventListener(window, 'beforeunload', beforeunload)
+
+  // contains an URL of function given to `requestToLeave`
+  // any truthy value will trigger the ExitConfirmationModal
+  const [modalDest, setModalDest] = useState(false)
+  const onCloseModalRequest = useCallback(() => {
+    setModalDest(false)
+  }, [setModalDest])
+
+  // call this with an URL or a function to trigger the ExitConfirmationModal
+  const requestToLeave = useCallback(
+    where => {
+      if (isActivated(state.current.activate)) {
+        setModalDest(() => where)
+      } else {
+        go(where)
+      }
+    },
+    [state, setModalDest]
+  )
+
+  // null when the modal is closed, a Modal otherwise
+  const onConfirm = useCallback(() => {
+    onLeave && onLeave()
+    go(modalDest)
+  }, [modalDest, onLeave])
+
+  // return the modal if opened
+  const modal = modalDest && (
+    <LocalizedConfirmModal
+      message={message}
+      title={title}
+      onCancel={onCloseModalRequest}
+      onConfirm={onConfirm}
+    />
+  )
+  return {
+    requestToLeave,
+    exitConfirmationModal: modal
+  }
+}
diff --git a/react/hooks/useConfirmExit/index.spec.js b/react/hooks/useConfirmExit/index.spec.js
new file mode 100644
index 0000000000..0e32017032
--- /dev/null
+++ b/react/hooks/useConfirmExit/index.spec.js
@@ -0,0 +1,183 @@
+import useConfirmExit from './'
+import { renderHook, act } from '@testing-library/react-hooks'
+import { isElement } from 'react-dom/test-utils'
+
+function triggerBeforeUnload() {
+  const event = new Event('beforeunload')
+  return window.dispatchEvent(event)
+}
+
+expect.extend({
+  toBeReactElement(received) {
+    if (received && isElement(received)) {
+      return {
+        pass: true,
+        message: `expected ${typeof received} should not be a React Element`
+      }
+    } else {
+      return {
+        pass: false,
+        message: `expected ${typeof received} should be a React Element`
+      }
+    }
+  },
+  toBeFunctionWithAtLeastArgs(received, number) {
+    if (
+      received &&
+      typeof received === 'function' &&
+      typeof received.length === 'number' &&
+      received.length >= number
+    ) {
+      return {
+        pass: true,
+        message: `expected ${typeof received} should be a function where arguments ${received &&
+          received.length} should be at least ${number}`
+      }
+    } else {
+      return {
+        pass: true,
+        message: `expected ${typeof received} should not be a function or where arguments ${received &&
+          received.length} should be less than ${number}`
+      }
+    }
+  }
+})
+
+const message = 'message'
+const onLeave = jest.fn()
+const where = jest.fn()
+
+describe('useConfirmExit', () => {
+  afterEach(() => {
+    jest.resetAllMocks()
+  })
+
+  describe('return values', () => {
+    describe('requestToLeave', () => {
+      it('should exist in returned values', async () => {
+        const { result } = renderHook(() =>
+          useConfirmExit({ message, onLeave })
+        )
+        expect(result.current).toHaveProperty('requestToLeave')
+      })
+
+      it('should be a function', async () => {
+        const { result } = renderHook(() =>
+          useConfirmExit({ message, onLeave })
+        )
+        expect(result.current.requestToLeave).toBeInstanceOf(Function)
+      })
+
+      it('should be accept an argument', async () => {
+        const { result } = renderHook(() =>
+          useConfirmExit({ message, onLeave })
+        )
+        expect(result.current.requestToLeave).toBeFunctionWithAtLeastArgs(1)
+      })
+
+      it('should trigger a react component in the modal', async () => {
+        const { rerender, result } = renderHook(() =>
+          useConfirmExit({ message, onLeave, activate: true })
+        )
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+        const requestToLeave = result.current.requestToLeave
+        act(() => requestToLeave(where))
+        rerender()
+        expect(result.current.exitConfirmationModal).toBeReactElement()
+      })
+
+      it('should not call the destination', async () => {
+        const { rerender, result } = renderHook(() =>
+          useConfirmExit({ message, onLeave, activate: true })
+        )
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+        const requestToLeave = result.current.requestToLeave
+        act(() => requestToLeave(where))
+        rerender()
+        expect(where).not.toHaveBeenCalled()
+      })
+
+      it('should not trigger a react component in the modal for activate=false', async () => {
+        const { rerender, result } = renderHook(() =>
+          useConfirmExit({ message, onLeave, activate: false })
+        )
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+        const requestToLeave = result.current.requestToLeave
+        act(() => requestToLeave(where))
+        rerender()
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+      })
+
+      it('should go to the destination for activate=false', async () => {
+        const { rerender, result } = renderHook(() =>
+          useConfirmExit({ message, onLeave, activate: false })
+        )
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+        const requestToLeave = result.current.requestToLeave
+        act(() => requestToLeave(where))
+        rerender()
+        expect(where).toHaveBeenCalledTimes(1)
+      })
+    })
+
+    describe('exitConfirmationModal', () => {
+      it('should exist in returned values', () => {
+        const { result } = renderHook(() =>
+          useConfirmExit({ message, onLeave })
+        )
+        expect(result.current).toHaveProperty('exitConfirmationModal')
+      })
+
+      it('should not be displayed be default', async () => {
+        const { result } = renderHook(() =>
+          useConfirmExit({ message, onLeave })
+        )
+        expect(result.current.exitConfirmationModal).toBeFalsy()
+      })
+    })
+  })
+
+  describe('onbeforeunload', () => {
+    it('should subscribe to window.onbeforeunload at mount', async () => {
+      renderHook(() => useConfirmExit({ message, onLeave }))
+
+      triggerBeforeUnload()
+      expect(onLeave).toHaveBeenCalledTimes(1)
+    })
+
+    it('should unsubscribe to window.onbeforeunload at dismount', async () => {
+      const { unmount } = renderHook(() => useConfirmExit({ message, onLeave }))
+      unmount()
+
+      triggerBeforeUnload()
+      expect(onLeave).toHaveBeenCalledTimes(0)
+    })
+
+    it('should allow a null message', async () => {
+      renderHook(() => useConfirmExit({ message, onLeave }))
+
+      triggerBeforeUnload()
+      expect(onLeave).toHaveBeenCalledTimes(1)
+    })
+
+    it('should allow a null callback', async () => {
+      renderHook(() => useConfirmExit({ message }))
+
+      triggerBeforeUnload()
+    })
+
+    it('should do nothing for activate=false', async () => {
+      renderHook(() => useConfirmExit({ message, onLeave, activate: false }))
+
+      triggerBeforeUnload()
+      expect(onLeave).not.toHaveBeenCalled()
+    })
+
+    it('should works for activate=true', async () => {
+      renderHook(() => useConfirmExit({ message, onLeave, activate: true }))
+
+      triggerBeforeUnload()
+      expect(onLeave).toHaveBeenCalledTimes(1)
+    })
+  })
+})
diff --git a/react/hooks/useConfirmExit/locales/en.json b/react/hooks/useConfirmExit/locales/en.json
new file mode 100644
index 0000000000..c7960fe10c
--- /dev/null
+++ b/react/hooks/useConfirmExit/locales/en.json
@@ -0,0 +1,8 @@
+{
+  "useConfirmExit": {
+    "back": "Back",
+    "leave": "Leave",
+    "title": "Do you want to leave?",
+    "message": "Some changes are not saved yet. Do you really want to leave and lose these changes?"
+  }
+}
diff --git a/react/hooks/useConfirmExit/locales/fr.json b/react/hooks/useConfirmExit/locales/fr.json
new file mode 100644
index 0000000000..57bc866a6a
--- /dev/null
+++ b/react/hooks/useConfirmExit/locales/fr.json
@@ -0,0 +1,8 @@
+{
+  "useConfirmExit": {
+    "back": "Retour",
+    "leave": "Quitter",
+    "title": "Voulez-vous quitter ?",
+    "message": "Des modifications n'ont pas encore pu ĂȘtre enregistrĂ©es. Voulez-vous vraiment quitter et perdre ces modifications ?"
+  }
+}