chore(deps): L3-0000 upgrade seldon dev deps to React 19 - #910
scottdickerson wants to merge 8 commits into
Conversation
Bumps the dev dependency on React from 18.3.1 to 19.2.8 while preserving the peer-dependency contract that already allowed both React 18 and React 19 consumers. Also brings along the React ecosystem packages whose peer types explicitly excluded React 19. Superseded by attempt #808 (react-dom bumped without react — inconsistent). ## Dependency changes Dev dependencies bumped: - react 18.3.1 → 19.2.8 - react-dom 18.2.0 → 19.2.8 - @types/react 18.3.3 → 19.2.17 - @types/react-dom 18.0.11 → 19.2.3 - @testing-library/react 16.0.0 → 16.3.2 (React 19 peer support) - usehooks-ts 3.1.0 → 3.1.1 (React 19 peer support) - @chromatic-com/storybook 5.0.1 → 5.2.1 (Storybook 10.6.x peer support) Added: - @testing-library/dom ^10.4.1 (peer requirement of @testing-library/react 16.x) peerDependencies unchanged — still declares `"react": "^18.3.1 || ^19.0.0"` and `"react-dom": "^18.2.0 || ^19.0.0"`, so consumers can stay on 18 or move to 19. ## Code changes for React 19 compat RefObject prop types widened from `RefObject<T>` to `RefObject<T | null>` (React 19 `useRef<T>(null)` now returns the narrower nullable type): - CarouselDot.tsx (`scrollableContainerRef`) - CountryPickerCountryList.tsx (`listRef`) - FavoritesCollectionTile.tsx (`imageRef`) - ComboBox.tsx (widened prop + cast for usehooks-ts `useOnClickOutside`) `useOnClickOutside` cast at call site (usehooks-ts still types as `RefObject<T>`) — Search.tsx, ComboBox.tsx. JSX namespace no longer global in React 19; switched to `React.JSX.Element` or added `import type { JSX }` — utils/index.tsx, DetailList.stories.tsx, ProgressWizard.stories.tsx. `React.Children` / `cloneElement` stricter — added explicit generics on `isValidElement<Props>()` and typed cloneElement calls: - DetailList.tsx, NavigationSubmenu.tsx, Toast.test.tsx, FilterMenu.tsx, ProgressWizard/utils.tsx. DatePicker.tsx — `useRef()` now requires an initial value; added `null`/`undefined`. Also removed `extends Record<string, unknown>` from DatePickerProps (was silently marking every prop `unknown`), typed forwardRef explicitly, made `locale` optional to match its `= 'en'` default. StatefulViewingsList.tsx — removed the same latent `extends Record<string, unknown>` bug; replaced with `extends Omit<ViewingsListProps, ...>` so the spread of forwarded props remains legal. Search.tsx — added `nodeRef={searchInputRef}` to `<CSSTransition>` so react-transition-group no longer calls the removed `ReactDOM.findDOMNode`. SeldonImage.tsx — switched the workaround `fetchpriority` (lowercase HTML attr) back to the proper camelCase `fetchPriority` which React 19 now handles natively; removed the accompanying `@ts-expect-error` and eslint-disable. Also guarded empty `src` with `src || undefined` to satisfy React 19's stricter empty-string warning. Button.test.tsx — React 19 hoists `<link>` elements to `<head>`, so the prefetch-link assertions now query `document.head`. ## Verification - npm run build → exit 0 (tsc + vite build) - npm run lint → exit 0 (tsc --noEmit, eslint, stylelint, markdownlint) - npx vitest run --project=unit → 1042 / 1042 passing Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for phillips-seldon ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Tip All tests passed and all changes approved!🟢 UI Tests: 476 tests unchanged |
|
🚀 Storybook preview is ready. • Preview: https://68b9f094608b90f3cfec5a06-ehovcwdqgj.chromatic.com/ |
There was a problem hiding this comment.
Pull request overview
This PR upgrades Seldon’s dev-time React toolchain to React 19 (while keeping the existing peer-dependency contract supporting both React 18 and 19), and updates component/test typings and a few implementation details to align with React 19’s stricter types and removed APIs.
Changes:
- Bump devDependencies to React 19 (plus aligned types/testing deps) and update lockfile accordingly.
- Update TypeScript types across components/stories/tests for React 19 changes (refs now nullable,
JSXnamespace changes, stricterChildren/cloneElementtyping). - Patch the only
react-transition-groupusage by providingnodeRef, and update DOM/test behavior changes (e.g.,fetchPriority,<link>hoisting).
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/index.tsx | Switches internal types away from global JSX to React.JSX for React 19 compatibility. |
| src/patterns/ViewingsList/StatefulViewingsList.tsx | Fixes props typing by extending Omit<ViewingsListProps, ...> to avoid the previous Record<string, unknown> typing issue. |
| src/patterns/ProgressWizard/utils.tsx | Tightens isValidElement generic typing to satisfy React 19 Children element typing. |
| src/patterns/ProgressWizard/ProgressWizard.stories.tsx | Imports JSX type explicitly for React 19 and uses it in story typing. |
| src/patterns/FilterMenu/FilterMenu.tsx | Adds explicit generic to isValidElement to keep cloneElement strongly typed under React 19. |
| src/patterns/FavoritesCollectionTile/FavoritesCollectionTile.tsx | Widens ref prop types to accept `RefObject<T |
| src/patterns/CountryPicker/CountryPickerCountryList.tsx | Widens ref prop types to accept `RefObject<T |
| src/components/Toast/Toast.test.tsx | Updates isValidElement/cloneElement typing in mocks to satisfy React 19 element typing. |
| src/components/SeldonImage/SeldonImage.tsx | Uses fetchPriority (React 19 native support) and avoids empty-string src warnings via `src |
| src/components/Search/Search.tsx | Avoids findDOMNode via CSSTransition nodeRef and casts refs for useOnClickOutside typing. |
| src/components/Navigation/NavigationSubmenu/NavigationSubmenu.tsx | Updates isValidElement/cloneElement generics and event typing for React 19 element narrowing. |
| src/components/DetailList/DetailList.tsx | Adds explicit isValidElement generic to keep child props typed under React 19. |
| src/components/DetailList/DetailList.stories.tsx | Imports JSX type explicitly for React 19 (no global namespace). |
| src/components/DatePicker/DatePicker.tsx | Fixes React 19 useRef initialization requirements and corrects the props typing regression from Record<string, unknown>. |
| src/components/DatePicker/DatePicker.stories.tsx | Updates useRef initialization for React 19. |
| src/components/ComboBox/ComboBox.tsx | Widens nullable ref prop type and casts for useOnClickOutside typing under React 19. |
| src/components/Carousel/CarouselDot.tsx | Widens ref prop types to accept `RefObject<T |
| src/components/Carousel/CarouselDot.test.tsx | Updates test ref typing to match updated CarouselDot prop types. |
| src/components/Button/Button.test.tsx | Adjusts assertions to account for React 19 <link> hoisting into document.head. |
| package.json | Bumps React/ReactDOM/types/testing/chromatic devDependencies and adds @testing-library/dom. |
| package-lock.json | Updates resolved dependency tree for the React 19 and related dev dependency upgrades. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Two behavior-preserving fixes flagged by code review:
1. SeldonImage — empty src no longer silently skips the error UI.
Previously `<img src="">` fired the browser onError event which
set loadingState to 'error' and showed the error icon. With
`src={src || undefined}` (to silence React 19's empty-src warning)
the error path never triggered. Now initialize loadingState to
'error' when !src, and short-circuit the async loadImage so it
doesn't fire a stray setState after unmount.
2. Search — CSSTransition classes now land on the wrapper div, not
the inner <input>. React-transition-group's findDOMNode used to
return Input's outermost DOM node (the wrapper <div>) so
`.seldon-input-enter` classes were applied there. Switching to
`nodeRef={searchInputRef}` shifted them to the input element
itself, a silent DOM-target change for any downstream consumer
styling those transition classes. Added a dedicated
searchInputWrapperRef and a wrapping div so nodeRef targets the
wrapper.
Verified: build passes, lint passes, 1042/1042 unit tests pass, no
unhandled rejections.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prior wrapper-div fix around <CSSTransition> introduced an extra block that disrupted the flex chain, pushing the input to a new row. Removes the wrapper and lands the transition classes on the <input> directly, then updates the SCSS selector from `.seldon-input-enter-active input` (descendant) to `.seldon-input__input.seldon-input-enter-active` so the enter-active background/placeholder styling still applies. Also: - Drops stale explanatory comments across Search, SeldonImage, and the Button test helper that documented the React-18→19 delta rather than the code's current behavior. - Adds .claude/worktrees to eslint ignores so lint doesn't fail on files that belong to a locked git worktree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prevents `.claude/worktrees/` from being accidentally added as a submodule entry when the pre-commit hook runs `git add .`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the eslint-config change so `npm run coverage` and the pre-push hook don't pick up test files in a locally-checked-out worktree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🤖 This comment was generated by an AI agent (thermo-nuclear code quality review). Outstanding fixes (type-boundary cleanliness):
|
Jira ticket
N/A — dependency housekeeping. Successor to #808 (which failed CI because it bumped
react-domwithout bumpingreact, breaking version alignment).Screenshots
Figma link
N/A.
Summary
Upgrades seldon's dev-time React from 18.3.1 to 19.2.8 (latest stable) while preserving the peer-dependency contract that already supports both React 18 and 19. Downstream consumers of
@phillips/seldoncan stay on React 18 today and migrate to React 19 on their own schedule — this PR only changes what seldon builds and tests against locally / in CI.WHY: React 19 has been stable since late 2024. Keeping seldon's dev dep on 18 means we never CI-test the library against 19, so any React-19-only regression (from a Radix update, a Storybook update, or our own code) would leak to consumers before we catch it. Also unblocks downstream
phillips-public-remixif/when it moves to 19.Change List (describe the changes made to the files)
Dependency changes (
package.json+package-lock.json):Bumped in devDependencies:
react18.3.1 → 19.2.8react-dom18.2.0 → 19.2.8@types/react18.3.3 → 19.2.17@types/react-dom18.0.11 → 19.2.3@testing-library/react16.0.0 → 16.3.2 (peer now^18.0.0 || ^19.0.0)usehooks-ts3.1.0 → 3.1.1 (peer now^18 || ^19)@chromatic-com/storybook5.0.1 → 5.2.1 (Storybook 10.6.x peer support)Added:
@testing-library/dom^10.4.1— now a required peer of@testing-library/react16.xNot changed:
peerDependenciesblock still declaresreact: ^18.3.1 || ^19.0.0andreact-dom: ^18.2.0 || ^19.0.0— the "consumers can pick either" contract from #808's era is preserved.Code changes to accommodate React 19's stricter types + removed APIs:
RefObject types — React 19
useRef<T>(null)returnsRefObject<T | null>(previouslyRefObject<T>). Prop types widened accordingly:CarouselDot.tsx(scrollableContainerRef)CountryPickerCountryList.tsx(listRef)FavoritesCollectionTile.tsx(imageRef)ComboBox.tsx(widened prop + cast for usehooks-tsuseOnClickOutside)useOnClickOutsidecall sites (Search.tsx,ComboBox.tsx) — usehooks-ts still types the parameter asRefObject<T>and doesn't accept the nullable form.JSX namespace — React 19 removed the global
JSXnamespace. Switched toReact.JSX.Elementor addedimport type { JSX } from 'react':utils/index.tsx,DetailList.stories.tsx,ProgressWizard.stories.tsxChildren / cloneElement — React 19 types
React.Children.mapcallback param asReactElement<unknown>. Added explicit generics onReact.isValidElement<Props>()and typedcloneElementcalls:DetailList.tsx,NavigationSubmenu.tsx,Toast.test.tsx,FilterMenu.tsx,ProgressWizard/utils.tsxDatePicker.tsx —
useRef()now requires an initial value. Also removedextends Record<string, unknown>fromDatePickerProps(a latent bug that was silently marking every declared propunknown). Madelocaleoptional to match its= 'en'default; typed the forwardRef signature explicitly.StatefulViewingsList.tsx — same latent
extends Record<string, unknown>bug removed; replaced withextends Omit<ViewingsListProps, ...>so the spread of forwarded props remains legal.Search.tsx —
react-transition-group@4.4.5's<CSSTransition>calls the removedReactDOM.findDOMNodeinternally. AddednodeRef={searchInputRef}so the transition finds the DOM node via ref instead. (No consumer changes needed — this is our only usage.)SeldonImage.tsx — the workaround
fetchpriority(lowercase HTML attr, with@ts-expect-error+ eslint-disable) is no longer needed; React 19 handlesfetchPriority(camelCase) natively. Also guarded emptysrcwithsrc || undefinedto satisfy React 19's stricter empty-string warning.Button.test.tsx — React 19 hoists
<link>elements to<head>for de-duplication. Prefetch-link assertions now querydocument.headdirectly.Acceptance Test (how to verify the PR)
src,fetchprioritycasing) which have been addressed at the source.@phillips/seldoninphillips-public-remixto a preview build of this branch and confirm the app still boots on React 18 (the consumer's current React version).phillips-public-remixitself to React 19 to validate the peer-dep contract end-to-end.Regression Test
<Search>expand/collapse animation still works (nodeRef change on CSSTransition).<Toast>still renders (usescloneElementwith typed generics now).<DatePicker>opens and picks a date (useRef initialization + forwardRef typing changed).<SeldonImage>renders withfetchPriority="high"— DOM attribute should still befetchpriority="high"(HTML is case-insensitive), just no more console warning.<CountryPicker>and<FavoritesCollectionTile>refs still forward correctly to their internal list containers.Evidence of testing
Local:
npm run build→ exit 0 (tsc + vite build clean)npm run lint→ exit 0 (tsc --noEmit, eslint, stylelint, markdownlint)npx vitest run --project=unit→ 1042 / 1042 passing on React 19Prior state on this branch had 83 test failures on React 19 — all fixed. The failures fell into predictable buckets:
findDOMNode-removed (fixed vianodeRef),fetchpriorityDOM warning (fixed via camelCase), empty-src warning (fixed via|| undefined), and hoisted<link>in tests (fixed by queryingdocument.head).Known caveats — please spot-check
--legacy-peer-depswas used at install time to resolve a Storybook 10.3.3 ↔ @storybook/react 10.5.5 peer mismatch that also exists on main today. The lockfile is consistent; the flag doesn't leave anything broken.react-transition-groupremains at 4.4.5 (no React-19-compatible stable release exists). Our single usage inSearch.tsxis patched vianodeRef— the standard workaround.@types/react18 → 19 is a moderately breaking API surface change for consumers writing generic React components.phillips-public-remixdoesn't use these types directly.Things to look for during review
feat(scope): ...if aminorrelease should be triggered.phillipsclass prefix are using the prefix variabledata-testidattribute.🤖 Generated with Claude Code