From f8479ed445485138e741a634b68bbcbaf0c0b7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 20:25:52 +0800 Subject: [PATCH 1/6] feat(switch): add @nextui-org/use-safe-layout-effect --- packages/components/switch/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/switch/package.json b/packages/components/switch/package.json index 98c276a4aa..a1bdbc834d 100644 --- a/packages/components/switch/package.json +++ b/packages/components/switch/package.json @@ -42,6 +42,7 @@ "dependencies": { "@nextui-org/shared-utils": "workspace:*", "@nextui-org/react-utils": "workspace:*", + "@nextui-org/use-safe-layout-effect": "workspace:*", "@react-aria/focus": "^3.16.2", "@react-aria/interactions": "^3.21.1", "@react-aria/switch": "^3.6.2", From 6bbda94d8ab905a259aad5b166d5ffa4b85190b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 20:26:08 +0800 Subject: [PATCH 2/6] chore(deps): add @nextui-org/use-safe-layout-effect --- pnpm-lock.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d19c64507a..e4243beaae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2502,6 +2502,9 @@ importers: '@nextui-org/shared-utils': specifier: workspace:* version: link:../../utilities/shared-utils + '@nextui-org/use-safe-layout-effect': + specifier: workspace:* + version: link:../../hooks/use-safe-layout-effect '@react-aria/focus': specifier: ^3.16.2 version: 3.16.2(react@18.2.0) @@ -5952,10 +5955,6 @@ packages: peerDependencies: '@effect-ts/otel-node': '*' peerDependenciesMeta: - '@effect-ts/core': - optional: true - '@effect-ts/otel': - optional: true '@effect-ts/otel-node': optional: true dependencies: @@ -22449,9 +22448,6 @@ packages: resolution: {integrity: sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==} engines: {node: '>= 12.0.0'} hasBin: true - peerDependenciesMeta: - '@parcel/core': - optional: true dependencies: '@parcel/config-default': 2.12.0(@parcel/core@2.12.0)(typescript@4.9.5) '@parcel/core': 2.12.0 From de70bf2a4d5f1a2b8ef8024bebb7ff6fa53ffbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 20:27:06 +0800 Subject: [PATCH 3/6] fix(switch): react-hook-form uncontrolled switch component --- .changeset/rotten-zoos-decide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-zoos-decide.md diff --git a/.changeset/rotten-zoos-decide.md b/.changeset/rotten-zoos-decide.md new file mode 100644 index 0000000000..d7e2c19803 --- /dev/null +++ b/.changeset/rotten-zoos-decide.md @@ -0,0 +1,5 @@ +--- +"@nextui-org/switch": patch +--- + +Fixed react-hook-form uncontrolled switch component From 1a760440080336656e53a292c5fab55cd1987157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 20:27:21 +0800 Subject: [PATCH 4/6] fix(switch): react-hook-form uncontrolled switch component --- packages/components/switch/src/use-switch.ts | 23 +++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/components/switch/src/use-switch.ts b/packages/components/switch/src/use-switch.ts index aa209fbc00..fbf8895535 100644 --- a/packages/components/switch/src/use-switch.ts +++ b/packages/components/switch/src/use-switch.ts @@ -1,15 +1,15 @@ import type {ToggleVariantProps, ToggleSlots, SlotsToClasses} from "@nextui-org/theme"; -import type {FocusableRef} from "@react-types/shared"; import type {AriaSwitchProps} from "@react-aria/switch"; import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system"; import {ReactNode, Ref, useCallback, useId, useRef, useState} from "react"; import {mapPropsVariants} from "@nextui-org/system"; +import {mergeRefs} from "@nextui-org/react-utils"; +import {useSafeLayoutEffect} from "@nextui-org/use-safe-layout-effect"; import {useHover, usePress} from "@react-aria/interactions"; import {toggle} from "@nextui-org/theme"; import {chain, mergeProps} from "@react-aria/utils"; import {clsx, dataAttr, objectToDeps} from "@nextui-org/shared-utils"; -import {useFocusableRef} from "@nextui-org/react-utils"; import {useSwitch as useReactAriaSwitch} from "@react-aria/switch"; import {useMemo} from "react"; import {useToggleState} from "@react-stately/toggle"; @@ -27,7 +27,7 @@ interface Props extends HTMLNextUIProps<"input"> { /** * Ref to the DOM node. */ - ref?: Ref; + ref?: Ref; /** * The label of the switch. */ @@ -100,8 +100,9 @@ export function useSwitch(originalProps: UseSwitchProps = {}) { const Component = as || "label"; - const inputRef = useRef(null); - const domRef = useFocusableRef(ref as FocusableRef, inputRef); + const domRef = useRef(null); + + const inputRef = useRef(null); const labelId = useId(); @@ -139,6 +140,16 @@ export function useSwitch(originalProps: UseSwitchProps = {}) { const state = useToggleState(ariaSwitchProps); + // if we use `react-hook-form`, it will set the switch value using the ref in register + // i.e. setting ref.current.checked to true or false which is uncontrolled + // hence, sync the state with `ref.current.checked` + useSafeLayoutEffect(() => { + if (!inputRef.current) return; + const isInputRefChecked = !!inputRef.current.checked; + + state.setSelected(isInputRefChecked); + }, [inputRef.current]); + const { inputProps, isPressed: isPressedKeyboard, @@ -212,7 +223,7 @@ export function useSwitch(originalProps: UseSwitchProps = {}) { const getInputProps: PropGetter = (props = {}) => { return { ...mergeProps(inputProps, focusProps, props), - ref: inputRef, + ref: mergeRefs(inputRef, ref), id: inputProps.id, onChange: chain(onChange, inputProps.onChange), }; From 2c61aab54c257899d248ffa7b3fbb9f164cf0342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 21:12:55 +0800 Subject: [PATCH 5/6] feat(switch): add rect-hook-form in dev dep --- packages/components/switch/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/switch/package.json b/packages/components/switch/package.json index a1bdbc834d..b93188a957 100644 --- a/packages/components/switch/package.json +++ b/packages/components/switch/package.json @@ -57,7 +57,8 @@ "@nextui-org/shared-icons": "workspace:*", "clean-package": "2.2.0", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "react-hook-form": "^7.51.3" }, "clean-package": "../../../clean-package.config.json" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4243beaae..dcb67beea4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2545,6 +2545,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.51.3 + version: 7.51.3(react@18.2.0) packages/components/table: dependencies: From a1f3cd1f515c334a515e08aea2c34a4b084ddcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D5=A1=C9=A8=D5=BC=C9=A2=D3=84=D5=A1=D6=85=D5=BC=C9=A2?= Date: Wed, 1 May 2024 21:13:24 +0800 Subject: [PATCH 6/6] feat(switch): add WithReactHookFormTemplate --- .../switch/stories/switch.stories.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/components/switch/stories/switch.stories.tsx b/packages/components/switch/stories/switch.stories.tsx index 33a753a70b..8564dfd7e3 100644 --- a/packages/components/switch/stories/switch.stories.tsx +++ b/packages/components/switch/stories/switch.stories.tsx @@ -5,6 +5,8 @@ import {toggle} from "@nextui-org/theme"; import {VisuallyHidden} from "@react-aria/visually-hidden"; import {SunFilledIcon, MoonFilledIcon} from "@nextui-org/shared-icons"; import {clsx} from "@nextui-org/shared-utils"; +import {button} from "@nextui-org/theme"; +import {useForm} from "react-hook-form"; import {Switch, SwitchProps, SwitchThumbIconProps, useSwitch} from "../src"; @@ -131,6 +133,44 @@ const CustomWithHooksTemplate = (args: SwitchProps) => { ); }; +const WithReactHookFormTemplate = (args: SwitchProps) => { + const { + register, + formState: {errors}, + handleSubmit, + } = useForm({ + defaultValues: { + defaultTrue: true, + defaultFalse: false, + requiredField: false, + }, + }); + + const onSubmit = (data: any) => { + // eslint-disable-next-line no-console + console.log(data); + alert("Submitted value: " + JSON.stringify(data)); + }; + + return ( +
+ + By default this switch is true + + + By default this switch is false + + + This switch is required + + {errors.requiredField && This switch is required} + +
+ ); +}; + export const Default = { args: { ...defaultProps, @@ -204,3 +244,11 @@ export const CustomWithHooks = { ...defaultProps, }, }; + +export const WithReactHookForm = { + render: WithReactHookFormTemplate, + + args: { + ...defaultProps, + }, +};