From 9205bcb5d0ad6ec5e32000ede05cfc4139c8c1f7 Mon Sep 17 00:00:00 2001 From: Raashish Aggarwal <94279692+raashish1601@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:53:08 +0530 Subject: [PATCH] fix react RadioGroup nullable controlled value typing --- .../src/components/radio-group/radio-group.tsx | 12 ++++++++---- .../src/hooks/use-controllable.ts | 6 +++--- .../@headlessui-react/types/radio-group.test.tsx | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 packages/@headlessui-react/types/radio-group.test.tsx diff --git a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx index f1de37f06..81cd41e2c 100644 --- a/packages/@headlessui-react/src/components/radio-group/radio-group.tsx +++ b/packages/@headlessui-react/src/components/radio-group/radio-group.tsx @@ -152,12 +152,12 @@ export type RadioGroupProps< TType = string, > = Props< TTag, - RadioGroupRenderPropArg, + RadioGroupRenderPropArg, RadioGroupPropsWeControl, { - value?: TType + value?: TType | null defaultValue?: TType - onChange?: (value: TType) => void + onChange?: (value: NoInfer) => void by?: ByComparator disabled?: boolean form?: string @@ -192,7 +192,11 @@ function RadioGroupFn( + controlledValue, + controlledOnChange, + defaultValue + ) let firstOption = useMemo( () => diff --git a/packages/@headlessui-react/src/hooks/use-controllable.ts b/packages/@headlessui-react/src/hooks/use-controllable.ts index eb5c16a63..383140d08 100644 --- a/packages/@headlessui-react/src/hooks/use-controllable.ts +++ b/packages/@headlessui-react/src/hooks/use-controllable.ts @@ -2,9 +2,9 @@ import { useRef, useState } from 'react' import { flushSync } from 'react-dom' import { useEvent } from './use-event' -export function useControllable( +export function useControllable( controlledValue: T | undefined, - onChange?: (value: T) => void, + onChange?: (value: TValue) => void, defaultValue?: T ) { let [internalValue, setInternalValue] = useState(defaultValue) @@ -30,7 +30,7 @@ export function useControllable( return [ (isControlled ? controlledValue : internalValue)!, - useEvent((value) => { + useEvent((value: TValue) => { if (isControlled) { return onChange?.(value) } else { diff --git a/packages/@headlessui-react/types/radio-group.test.tsx b/packages/@headlessui-react/types/radio-group.test.tsx new file mode 100644 index 000000000..9ebaeb5d6 --- /dev/null +++ b/packages/@headlessui-react/types/radio-group.test.tsx @@ -0,0 +1,15 @@ +import React, { useState } from 'react' +import { Radio, RadioGroup } from '../src' + +function ControlledNullableRadioGroup() { + let [selected, setSelected] = useState(undefined) + + return ( + + Startup + Business + + ) +} + +void ControlledNullableRadioGroup