Skip to content

refactor: update ref types in components for better null handling#1289

Open
vinitkhandal717 wants to merge 2 commits intodevfrom
1282-bug-juspayblend-design-system-bundles-react-19-internals-incompatible-with-react-18
Open

refactor: update ref types in components for better null handling#1289
vinitkhandal717 wants to merge 2 commits intodevfrom
1282-bug-juspayblend-design-system-bundles-react-19-internals-incompatible-with-react-18

Conversation

@vinitkhandal717
Copy link
Copy Markdown
Collaborator

Summary

update the ref and fix the react 18 issue

Issue Ticket

Closes #1282

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix the React 18 crash reported in #1282 by preventing react-dom from being bundled into the library output, and also adjusts several component ref/type definitions for React 18/19 type compatibility and null handling.

Changes:

  • Update Vite/Rollup library build externals to keep react-dom (and styled-components) out of the bundle.
  • Refactor multiple components/types to adjust useRef and ref prop typings.
  • Update Block’s styled-components shouldForwardProp handling (and related type refactors).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/blend/vite.config.ts Externalizes react-dom (and styled-components) to avoid bundling ReactDOM internals.
packages/blend/lib/components/shared/accessibility/AccessibilityDashboard.tsx Loosens accessibility component registry prop typing.
packages/blend/lib/components/Timeline/Timeline.tsx Ref nullability + forwarded ref assignment casting update.
packages/blend/lib/components/Tabs/TabsList.tsx Ref nullability update for tabs list container ref.
packages/blend/lib/components/SingleSelectV2/singleSelectV2.types.ts Updates ref-related prop types for SingleSelectV2 menu/virtual list.
packages/blend/lib/components/Primitives/PrimitiveInput/PrimitiveInput.tsx Simplifies forwardRef component typing.
packages/blend/lib/components/Primitives/Block/Block.tsx Updates prop filtering and Block prop typing (styled-components v6-related).
packages/blend/lib/components/MultiSelectV2/MultiSelectV2MenuSearch.tsx Updates input ref prop type.
packages/blend/lib/components/MultiSelectV2/MultiSelectV2MenuHeader.tsx Updates search input ref prop type.
packages/blend/lib/components/InputsV2/utils/InputSlots/InputSlots.tsx Updates slot ref prop type.
packages/blend/lib/components/InputsV2/TextInputV2/TextInputV2.tsx Ref nullability update for inputRef.
packages/blend/lib/components/Directory/NavItem.tsx Ref nullability update for union element ref.
packages/blend/lib/components/DateRangePicker/CalendarGrid.tsx Ref nullability update for scroll container ref.
packages/blend/lib/components/DataTable/DataTable.tsx Ref nullability update + forwarded ref assignment casting update.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

searchValue: string
searchPlaceholder?: string
searchInputRef: React.RefObject<HTMLInputElement | null>
searchInputRef: React.RefObject<HTMLInputElement>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchInputRef is created with useRef(null) upstream and is null until the input mounts. With @types/react 19, RefObject<T> does not include null in .current, so this should remain React.RefObject<HTMLInputElement | null> to avoid incorrect non-null typing and assignability issues when passing useRef<HTMLInputElement>(null).

Suggested change
searchInputRef: React.RefObject<HTMLInputElement>
searchInputRef: React.RefObject<HTMLInputElement | null>

Copilot uses AI. Check for mistakes.
type InputSlotProps = {
position: InputSlotPosition
slotRef: React.RefObject<HTMLDivElement | null>
slotRef: React.RefObject<HTMLDivElement>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slotRef is passed a ref created with useRef(null) (e.g. in TextInputV2), so .current is nullable. Under @types/react 19, RefObject<HTMLDivElement> implies non-null .current; this should be React.RefObject<HTMLDivElement | null> to match actual lifecycle behavior and avoid type incompatibilities at call sites.

Suggested change
slotRef: React.RefObject<HTMLDivElement>
slotRef: React.RefObject<HTMLDivElement | null>

Copilot uses AI. Check for mistakes.
Comment on lines +234 to +241
// styled-components v6 expects a `ShouldForwardProp` signature that takes the
// element target as the 2nd argument (not the defaultValidatorFn).
const shouldForwardProp: ShouldForwardProp<'web'> = (
prop,
elementToBeCreated
) => {
void elementToBeCreated // required by the type; not needed for our filtering
if (blockedProps.has(prop)) return false
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

styled(...).withConfig({ shouldForwardProp }) uses the v6 per-component shouldForwardProp callback shape where the 2nd argument is the default validator (when provided), not the element target. The current implementation/comment treats it as an element and ignores the default validator, which can cause styled-components to forward props it would normally filter (e.g. internal props) and the comment is misleading. Consider updating the callback to accept/compose with the provided default validator and adjust the comment accordingly.

Suggested change
// styled-components v6 expects a `ShouldForwardProp` signature that takes the
// element target as the 2nd argument (not the defaultValidatorFn).
const shouldForwardProp: ShouldForwardProp<'web'> = (
prop,
elementToBeCreated
) => {
void elementToBeCreated // required by the type; not needed for our filtering
if (blockedProps.has(prop)) return false
// styled-components v6 per-component `shouldForwardProp` receives the default
// validator as the 2nd argument. We first apply our own blocked prop filter,
// then delegate to the default validator (when provided).
const shouldForwardProp: ShouldForwardProp<'web'> = (
prop,
defaultValidator
) => {
if (blockedProps.has(prop)) return false
if (typeof defaultValidator === 'function') {
return defaultValidator(prop)
}

Copilot uses AI. Check for mistakes.
Comment on lines 190 to +232
'boxShadow',
'cursor',
'overflow',
'overflowX',
'overflowY',
'whiteSpace',
'transition',
// Positioning
'position',
'inset',
'top',
'right',
'bottom',
'left',
'zIndex',
'pointerEvents',
'opacity',
// Pseudo states
'_hover',
'_focus',
'_active',
'_disabled',
'_visited',
'_focusVisible',

//font
'fontSize',
'fontWeight',
'lineHeight',
'letterSpacing',
'textAlign',
'textTransform',
'userSelect',

'transform',
'willChange',
'transformOrigin',
'backfaceVisibility',
'transition',
'transitionDuration',
'transitionTimingFunction',
'transitionDelay',
]

const shouldForwardProp = (prop: string) => !blockedProps.includes(prop)

])
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blockedProps contains duplicate entries (e.g. transition appears more than once). With a Set this is functionally harmless, but it’s still extra noise and makes it easier to miss real changes to the list. Consider deduplicating the entries.

Copilot uses AI. Check for mistakes.
Comment on lines 95 to 110
measureElement: (node: Element | null) => void
loadingComponent?: ReactNode
hasMore?: boolean
virtualScrollRef: RefObject<HTMLDivElement | null>
virtualScrollRef: RefObject<HTMLDivElement>
}

export type MenuSearchProps = {
enabled?: boolean
hasItems: boolean
backgroundColor: string
searchPlaceholder: string
searchText: string
onSearchTextChange: (value: string) => void
searchInputRef: RefObject<HTMLInputElement | null>
containerRef?: RefObject<HTMLDivElement | null>
searchInputRef: RefObject<HTMLInputElement>
containerRef?: RefObject<HTMLDivElement>
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefObject<T> in @types/react 19 has a non-null .current (it does not implicitly include null). Refs created with useRef(null) / createRef() are typically RefObject<T | null>, and this component dereferences virtualScrollRef.current conditionally in multiple places. Changing these props to RefObject<HTMLDivElement> / RefObject<HTMLInputElement> makes the types inaccurate and can also break callers passing the common useRef<T>(null) ref object. Suggest switching these back to RefObject<... | null> (and keeping null checks where appropriate).

Copilot uses AI. Check for mistakes.

export type MultiSelectV2MenuSearchProps = {
inputRef: RefObject<HTMLInputElement | null>
inputRef: RefObject<HTMLInputElement>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefObject<T> in @types/react 19 models a non-null .current. Since inputRef will be null before mount (and the parent provides it via useRef(null)), this prop should be typed as RefObject<HTMLInputElement | null>; otherwise common call sites (e.g. useRef<HTMLInputElement>(null)) become incompatible and the type incorrectly implies .current is always available.

Suggested change
inputRef: RefObject<HTMLInputElement>
inputRef: RefObject<HTMLInputElement | null>

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: @juspay/blend-design-system bundles React 19 internals, incompatible with React 18

3 participants