From 5c98bb6c0f313ce336dfa1ecbf0f0eada0137b35 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:24:08 +0100 Subject: [PATCH] chore: `react-hook-form` best practices (#44221) ## Problem The design system documentation and examples promote an invalid usage of react-hook-form state. ## Solution - Fix the documentation and examples - Update Claude skills for future components ## Out of scope - Fix current code across the repository --- .cursor/rules/studio/forms/RULE.md | 5 ++--- apps/design-system/content/docs/ui-patterns/forms.mdx | 2 +- apps/design-system/content/docs/ui-patterns/modality.mdx | 7 ++++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.cursor/rules/studio/forms/RULE.md b/.cursor/rules/studio/forms/RULE.md index b9b729111b5..74f8383f862 100644 --- a/.cursor/rules/studio/forms/RULE.md +++ b/.cursor/rules/studio/forms/RULE.md @@ -1,5 +1,5 @@ --- -description: "Studio: form patterns (page layouts + side panels) and react-hook-form conventions" +description: 'Studio: form patterns (page layouts + side panels) and react-hook-form conventions' globs: - apps/studio/**/*.{ts,tsx} alwaysApply: false @@ -29,7 +29,6 @@ Use the Design System UI pattern docs as the source of truth: ## Actions and state -- Handle dirty state (`form.formState.isDirty`) to show Cancel and to disable Save. +- Handle dirty state by destructuring `isDirty` from `formState` (`const { isDirty } = form.formState`) then use it to show Cancel and to disable Save. - Show loading on submit buttons via `loading`. - When submit button is outside the `
`, set a stable `formId` and use the button’s `form` prop. - diff --git a/apps/design-system/content/docs/ui-patterns/forms.mdx b/apps/design-system/content/docs/ui-patterns/forms.mdx index 8e02ea0f90f..f37ab471ce9 100644 --- a/apps/design-system/content/docs/ui-patterns/forms.mdx +++ b/apps/design-system/content/docs/ui-patterns/forms.mdx @@ -49,7 +49,7 @@ Use the shared [Key/Value Field Array](../fragments/key-value-field-array) fragm 4. **Use Cards for grouping**: Wrap form sections in `Card` components with `CardContent` and `CardFooter` for actions. -5. **Handle dirty state**: Show cancel buttons and disable save buttons based on `form.formState.isDirty`. +5. **Handle dirty state**: Show cancel buttons and disable save buttons based on `form.formState.isDirty`. Make sure you destructure `isDirty` from `form.formState` (see https://react-hook-form.com/docs/useform/formstate) 6. **Error handling**: Always use mutations with `onSuccess` and `onError` callbacks that show toast notifications. diff --git a/apps/design-system/content/docs/ui-patterns/modality.mdx b/apps/design-system/content/docs/ui-patterns/modality.mdx index 9c4358cce0c..63938e858f5 100644 --- a/apps/design-system/content/docs/ui-patterns/modality.mdx +++ b/apps/design-system/content/docs/ui-patterns/modality.mdx @@ -104,8 +104,13 @@ Studio implementation (preferred in Studio code): import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog' import { useConfirmOnClose } from 'hooks/ui/useConfirmOnClose' +const form = useForm(...) +// Always destructure formState values otherwise they won't be updated +// See https://react-hook-form.com/docs/useform/formstate +const { isDirty } = form.formState + const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({ - checkIsDirty: () => form.formState.isDirty, + checkIsDirty: () => isDirty, onClose, })