Feature/dev/is 11570 haapi stepper history navigation alternative - #279
Conversation
…y. Fix stepper history in POST-redirect-GET scenarios.
Brilliant simplification @luisgoncalves ❤️ ! Some notes:
|
There was a problem hiding this comment.
Pull request overview
This PR spikes an alternative approach to HAAPI stepper history navigation by adjusting how stepper history entries are recorded (especially around automatic redirections), and by syncing those history changes into the browser’s URL/history state.
Changes:
- Update
HaapiSteppernext-step processing to carry “triggered by” action/payload through automatic redirections so history entries reflect the final GET step. - Extend the browser history adapter to optionally update the URL when pushing/replacing history entries.
- Rework
useHaapiStepperHistoryNavigationto encode reproducibility into the browser history state and to push/replace entries accordingly.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper.tsx | Propagates “triggered by” action/payload through redirect handling and normalizes action objects for downstream consumers. |
| src/haapi-react-sdk/haapi-stepper/feature/stepper/data-formatters/format-next-step-data.ts | Adds a more specific overload for form actions to support redirect-following logic. |
| src/haapi-react-app/src/shared/util/browser-apis.ts | Allows pushState/replaceState to optionally update the browser URL. |
| src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts | Changes the browser-history state model and updates push/replace behavior (including URL updates). |
Suppressed comments (2)
src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts:81
- Polling steps are marked non-reproducible by
isReproducibleHistoryEntry, but this hook still writes them into browser history and derivesurlfrom the polling action'smodel.href. That can change the address bar to the internal poll endpoint and introduces an extra history entry when polling starts (previous behavior/tests skipped polling entirely).
const currentBrowserEntry = browserNavigation.getState() as BrowserHistoryEntryIndex | undefined | null;
const newStepperEntry = stepperHistory[stepperHistory.length - 1];
src/haapi-react-app/src/shared/feature/history/useHaapiStepperHistoryNavigation.ts:112
syncStepperHistoryOnBrowserHistoryChangeusesReact.RefObjectbut this file doesn't importReact, which will fail typechecking. Use the already-importedRefObjecttype instead (and fix the malformed spacing in the signature).
function syncStepperHistoryOnBrowserHistoryChange(
currentBrowserHistoryEntry: BrowserHistoryEntryIndex | null,
haapiStepperRef: React.RefObject<HaapiStepperAPI> ,
browserNavigation: HistoryNavigation): void {
if (!currentBrowserHistoryEntry) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** Updates the current history entry's state, keeping the current URL (reuses the entry). */ | ||
| replaceEntry(state: unknown): void; | ||
| replaceEntry(state: unknown, url?: string): void; | ||
|
|
||
| /** Appends a new history entry (keeping the current URL), discarding any forward entries. */ | ||
| pushEntry(state: unknown): void; | ||
|
|
||
| /** Moves the browser through its history by `delta` entries (negative = back, positive = forward). */ | ||
| go(delta: number): void; | ||
| pushEntry(state: unknown, url?: string): void; |
| type BrowserHistoryEntryIndex = { | ||
| reproducible: true; | ||
| action: HaapiStepperNextStepAction; | ||
| payload?: HaapiStepperNextStepPayload; | ||
| } | ||
|
|
||
| interface BrowserHistoryEntryIndex { | ||
| index: number; | ||
| } | ||
|
|
||
| interface BrowserHistory { | ||
| entries: BrowserHistoryEntryData[]; | ||
| index: number; | ||
| } | ||
| } | { | ||
| reproducible: false; | ||
| }; |
| import { browserHistoryNavigation, type HistoryNavigation } from '../../util/browser-apis'; | ||
| import { isReproducibleHistoryEntry } from './reproducible-action'; | ||
|
|
||
| interface BrowserHistoryEntryData { |
There was a problem hiding this comment.
index seems too limited (number) to describe the data it contains.
| reproducible: false | ||
| }; | ||
|
|
||
| const url = 'href' in newStepperEntry.triggeredByAction |
There was a problem hiding this comment.
This third occurrence of 'href' in X probably deserves a util method (isLink) that avoids duplication while documenting the intent.
|
|
||
| const url = 'href' in newStepperEntry.triggeredByAction | ||
| ? newStepperEntry.triggeredByAction.href | ||
| : newStepperEntry.triggeredByAction.subtype === 'form' |
There was a problem hiding this comment.
Probably HAAPI_ACTION_TYPES.FORM. It should have warn you 🤔
|
|
||
| async function processHaapiNextStep(params: ProcessHaapiNextStepParams): Promise<{ | ||
| nextStepData?: HaapiStepperStep | null; | ||
| nextStepData?: { |
There was a problem hiding this comment.
Probably already accounted for, but just in case so I don't forget:
- We could create the
nextStepDatainterface and thenHaapiStepperHistoryEntryextends it with thetimestamp - Helpers (
handlePollingStep,handleAuthenticationOrRegistrationStep,handleCompletedStep) should probably returnnextStepData. - Some redundancy in the
nextStepDataconstruction. Maybe a helpergetNextStepDatacould simplify.
Thanks! As we discussed on the side, I'll put up smaller PRs on top of the base PR for the different changes I drafted here. I'll keep this one as draft while I do that, but I'll drop it in the end. |
Proposal on top of #266. This is a quick spike; typings are not final and I didn't update tests.