Add useBackButton hook - #60
Conversation
gabeabrams
left a comment
There was a problem hiding this comment.
Great structure! See comments
| /*------------------------------------------------------------------------*/ | ||
|
|
||
| // Marker stored on history entries owned by this hook | ||
| const HISTORY_STATE_MARKER = { dceReactkitBackButton: true }; |
|
|
||
| // Default message shown when the user tries to go back while blocked | ||
| const BLOCKED_TITLE = 'Cannot Go Back'; | ||
| const BLOCKED_MESSAGE = 'We are currently working. Please try again in a moment.'; |
There was a problem hiding this comment.
We're trying to move away from using "we" because it's ambiguous. How about:
"An active task is in progress. Please try again later."
Can you further improve upon this language?
|
|
||
| // Default message shown when the user tries to go back with unsaved changes | ||
| const UNSAVED_CHANGES_TITLE = 'Abandon Changes?'; | ||
| const UNSAVED_CHANGES_MESSAGE = 'Your current progress will be lost.'; |
There was a problem hiding this comment.
How about "Any unsaved changes may be lost."
|
|
||
| // True while a confirmation/alert is on screen, so repeated back presses don't | ||
| // stack up duplicate prompts | ||
| let promptVisible = false; |
There was a problem hiding this comment.
So many variables. Are they interdependent? If so, maybe we make a "BackButtonState" type and store it as one variable with rules about its structure?
| customBlockedMessage = undefined; | ||
|
|
||
| // Update the app | ||
| handleGoHome?.(); |
There was a problem hiding this comment.
I think we need to throw an error if handleGoHome isn't defined right?
| * Undo a back navigation that we don't want to allow, keeping the user in | ||
| * place. The browser's popstate event cannot be canceled, so we immediately | ||
| * push a replacement entry instead. Must be called synchronously while | ||
| * handling the pop. |
There was a problem hiding this comment.
Fascinating hack that might get us in trouble with the browser? haha >.< But interesting
| * @author Yuen Ler Chow | ||
| */ | ||
| export const backButtonController: BackButtonController = { | ||
| onSubpanelEntered: () => { |
There was a problem hiding this comment.
JSDoc for each function
|
|
||
| // Check whether the user is allowed to leave right now | ||
| if (!force) { | ||
| if (promptVisible) { |
There was a problem hiding this comment.
Explain this logic via comment
| // Keep the handler up to date so the listener never calls a stale version | ||
| handleGoHome = handleGoHomeFunc; | ||
|
|
||
| useEffect( |
There was a problem hiding this comment.
Explain this use of useEffect and why we need it
| */ | ||
| enum BackState { | ||
| // The user can go back immediately, no confirmation required | ||
| Normal = 'normal', |
There was a problem hiding this comment.
Use our typical formatting:
Normal = 'Normal',
* fix gaps between items * remove unnnecessary comment
What
Adds a useBackButton hook so the browser's back button navigates within the app instead of exiting it.
How it's used
Call it once at the top level with a handler that returns home, then use the exported backButtonController to mark when a subpanel is entered and to declare its state:
useBackButton(() => dispatch({ type: ShowHome }));
backButtonController.onSubpanelEntered();
backButtonController.setSubpanelState(BackState.UnsavedChanges);
The library handles the rest — back goes straight home when Normal, asks for confirmation on UnsavedChanges, and explains why it can't leave when Blocked. It uses reactkit's existing alert/confirm, so no new dependencies.
Notes
Exports useBackButton, backButtonController, and the BackState / BackButtonController types. Controller state is module-level so subpanels don't need prop drilling. Assumes a single level of navigation.
The fiddly parts are handled internally: popstate can't be canceled, so blocking works by synchronously pushing a replacement entry before any dialog is awaited — and consuming the history entry is asymmetric depending on whether the browser back button or an in-app button triggered it.