Repository navigation
ui: Stepper warns about duplicate step values at registration time - #111567
Conversation
Jetpack Cloud Live (direct link)
Automattic for Agencies Live (direct link)
|
|
This PR modifies the release build for the following Calypso Apps: For info about this notification, see here: PCYsg-OT6-p2
To test WordPress.com changes, run |
The duplicate-value warning in Stepper.Root was dead code: registration de-duped steps before the warning loop could ever see a duplicate. Move the warning into useStepRegistration where it can actually fire, and return a no-op deregister for rejected duplicates so unmounting one no longer removes the original step registration. Follow-up to #111036 review feedback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Stepper component is still unreleased, so a bug fix to it needs no changelog entry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
60b235b to
02bc01c
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes Stepper duplicate-step-value handling by moving the duplicate-value dev warning to step registration (where duplicates are actually detected) and ensuring rejected duplicate registrations don’t deregister the original step on unmount. It also updates tests to cover the warning and the corrected unmount behavior.
Changes:
- Move duplicate-step-value dev warning from
Stepper.RoottouseStepRegistration. - Return a no-op deregister function for rejected duplicate step registrations to avoid removing the original step.
- Add unit tests for the registration-time warning and for preserving the original step when a duplicate unmounts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/ui/src/stepper/use-step-registration.ts | Adds registration-time duplicate detection and changes deregistration behavior for duplicates. |
| packages/ui/src/stepper/test/index.tsx | Adds tests for the duplicate-value warning and for duplicate unmount not affecting the original. |
| packages/ui/src/stepper/root.tsx | Removes now-dead duplicate scanning warning and documents that duplicates are handled at registration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…p a step A rejected duplicate previously got a no-op deregister, so if the originally-accepted instance unmounted while the duplicate stayed mounted, the value lost its registration entirely. Track a per-value mount count and remove the entry only when the last mounted instance deregisters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The union-keys comparison in updateStep was needed when registration meta carried ReactNode snapshots; meta is now flat scalars, so a plain shallow compare (with a key-count check for removed fields) is enough. Also drop children/className from StepperPanelProps since ComponentProps<'div'> already provides them. Follow-up to #111036 review feedback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Document the stale-metadata limitation when duplicate step values overlap - Rename use-step-registration test to .ts (no JSX) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…etadata Instead of keying steps by value, each Stepper.Step now registers under a unique useId. The public steps list is derived as one entry per value from the first still-mounted instance, so a duplicate value can no longer override or leave stale metadata on the surviving step. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Order derived steps by each value's stable first-appearance slot instead of its current array position, so when an earlier duplicate instance unmounts the surviving instance keeps the value's original slot rather than jumping to the end. Adds tests for the slot invariant, the duplicate warning, and strengthens the surviving-metadata assertion to cover both mounted and unmounted states. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Looks like one of the E2E tests has failed. You can fix them following these steps:
|
ciampo
left a comment
There was a problem hiding this comment.
Good to merge once remaining feedback is addressed
| const steps = useMemo( () => { | ||
| const order = slotOrder.current; | ||
| const canonical = new Map< string, T >(); | ||
| for ( const inst of instances ) { | ||
| if ( ! canonical.has( inst.meta.value ) ) { | ||
| canonical.set( inst.meta.value, inst.meta ); | ||
| } | ||
| } | ||
| return [ ...canonical.values() ].sort( | ||
| ( a, b ) => ( order.get( a.value ) ?? 0 ) - ( order.get( b.value ) ?? 0 ) | ||
| ); | ||
| }, [ instances ] ); |
There was a problem hiding this comment.
Nit:
This memo reads slotOrder.current during render but only depends on [ instances ].
It happens to work because every slot mutation is paired with a setInstances, so the ref is always current by the time the memo reruns. Still, reading a mutable ref mid-render isn't pure and could bite us under concurrent rendering. Not worth blocking over — duplicates are already an error state — but if we want it airtight, folding the slot into the instances state ({ id, slot, meta }) and sorting on inst.slot drops the ref read entirely.
There was a problem hiding this comment.
Good call, done. Folded the slot into the instances state ({ id, slot, meta }) and the memo now sorts on inst.slot, so it no longer reads slotOrder.current mid-render. The ref is still used inside registerStep (outside render), so the derivation is now pure. Slot-pinning behavior is unchanged and still covered by the existing tests.
Fold the per-value slot into the instances state and sort on it, so the steps memo no longer reads the slotOrder ref mid-render — keeping the derivation pure under concurrent rendering. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Part of the follow-up work for #111036 review feedback (#111036 (comment), #111036 (comment), #111036 (comment)).
Proposed Changes
Stepper.RootintouseStepRegistration, where it can actually fire. The previous warning loop inroot.tsxwas dead code: registration silently de-duped steps, so the steps array it inspected could never contain duplicates.registerStep: only the first registration adds a state entry (later ones warn), and the entry is removed only when the last mounted instance for that value deregisters. Transient duplicates in either unmount order no longer drop a still-mounted step or skew indices andtotalSteps.updateStepcomparison to a plain shallow compare. The union-keys machinery was needed when registration meta carried ReactNode snapshots; meta is now flat scalars, and the key-count check still catches fields removed between renders (e.g.status).children/classNamefromStepperPanelProps;ComponentProps<'div'>already provides them.updateStep(written against the old comparison and kept green through the refactor).Why are these changes being made?
updateStepcomparison could be simplified after the registration-model refactor, and that the panel prop type duplicatedComponentProps<'div'>fields.Testing Instructions
yarn test-packages packages/ui/src/stepperand confirm all tests pass, including the "Stepper dev warnings" block anduse-step-registration.tsx.yarn typecheck-packagesto confirm theStepperPanelPropschange compiles.valueand confirm a one-time console warning "Two steps share value ..." appears in dev mode.Pre-merge Checklist