diff --git a/.yarn/versions/52f80f42.yml b/.yarn/versions/52f80f42.yml new file mode 100644 index 000000000..6ba30a2d7 --- /dev/null +++ b/.yarn/versions/52f80f42.yml @@ -0,0 +1,33 @@ +releases: + "@radix-ui/react-context": patch + "@radix-ui/react-form": patch + +declined: + - primitives + - "@radix-ui/react-accordion" + - "@radix-ui/react-alert-dialog" + - "@radix-ui/react-avatar" + - "@radix-ui/react-checkbox" + - "@radix-ui/react-collapsible" + - "@radix-ui/react-collection" + - "@radix-ui/react-context-menu" + - "@radix-ui/react-dialog" + - "@radix-ui/react-dropdown-menu" + - "@radix-ui/react-hover-card" + - "@radix-ui/react-menu" + - "@radix-ui/react-menubar" + - "@radix-ui/react-navigation-menu" + - "@radix-ui/react-popover" + - "@radix-ui/react-popper" + - "@radix-ui/react-progress" + - "@radix-ui/react-radio-group" + - "@radix-ui/react-roving-focus" + - "@radix-ui/react-scroll-area" + - "@radix-ui/react-select" + - "@radix-ui/react-slider" + - "@radix-ui/react-switch" + - "@radix-ui/react-tabs" + - "@radix-ui/react-toast" + - "@radix-ui/react-toggle-group" + - "@radix-ui/react-toolbar" + - "@radix-ui/react-tooltip" diff --git a/packages/react/context/src/createContext.tsx b/packages/react/context/src/createContext.tsx index e3ca734f7..aa2cf530a 100644 --- a/packages/react/context/src/createContext.tsx +++ b/packages/react/context/src/createContext.tsx @@ -63,12 +63,18 @@ function createContextScope(scopeName: string, createContextScopeDeps: CreateSco return {children}; } - function useContext(consumerName: string, scope: Scope) { + function useContext( + consumerName: string, + scope: Scope, + options: { isOptional?: boolean } = {} + ) { const Context = scope?.[scopeName][index] || BaseContext; const context = React.useContext(Context); + const { isOptional } = options; + if (isOptional) return {} as ContextValueType; if (context) return context; if (defaultContext !== undefined) return defaultContext; - // if a defaultContext wasn't specified, it's a required context. + // if a defaultContext wasn't specified and isOptional is false, it's a required context. throw new Error(`\`${consumerName}\` must be used within \`${rootComponentName}\``); } diff --git a/packages/react/form/src/Form.stories.tsx b/packages/react/form/src/Form.stories.tsx index 3d6e20245..5dd30fe59 100644 --- a/packages/react/form/src/Form.stories.tsx +++ b/packages/react/form/src/Form.stories.tsx @@ -107,10 +107,11 @@ export const Cypress = () => { Name (required) - valid! + {/* This Form.Message is used out of the Form.Field context */} + Age (0-99) diff --git a/packages/react/form/src/Form.tsx b/packages/react/form/src/Form.tsx index 834bc4147..ebe7d813f 100644 --- a/packages/react/form/src/Form.tsx +++ b/packages/react/form/src/Form.tsx @@ -462,9 +462,17 @@ interface FormMessageProps extends Omit { const FormMessage = React.forwardRef( (props: ScopedProps, forwardedRef) => { const { match, name: nameProp, ...messageProps } = props; - const fieldContext = useFormFieldContext(MESSAGE_NAME, props.__scopeForm); + const fieldContext = useFormFieldContext(MESSAGE_NAME, props.__scopeForm, { + isOptional: !!nameProp, + }); const name = nameProp ?? fieldContext.name; + if (!nameProp && !fieldContext?.name) { + throw new Error( + `\`${MESSAGE_NAME}\` must be used within \`${FIELD_NAME}\` or specify the \`name\` prop` + ); + } + if (match === undefined) { return (