{"record":{"id":"a216d242e259a410","repo":"appsmithorg/appsmith","slug":"formcontrol-compound-components-cannot-be-rendered","errorCode":null,"errorMessage":"FormControl compound components cannot be rendered outside the FormControl component","messagePattern":"FormControl compound components cannot be rendered outside the FormControl component","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/client/packages/design-system/ads/src/FormControl/FormControl.context.ts","lineNumber":16,"sourceCode":"import { createContext, useContext } from \"react\";\nimport type { FormControlProps } from \"./FormControl.types\";\n\nexport const FormControlContext = createContext<\n  Pick<FormControlProps, \"isRequired\" | \"isDisabled\" | \"size\">\n>({\n  isRequired: false,\n  isDisabled: false,\n  size: \"sm\",\n});\n\nexport const useFormControlContext = () => {\n  const context = useContext(FormControlContext);\n\n  if (!context) {\n    throw new Error(\n      \"FormControl compound components cannot be rendered outside the FormControl component\",\n    );\n  }\n\n  return context;\n};\n\nexport const FormControlProvider = FormControlContext.Provider;\n","sourceCodeStart":1,"sourceCodeEnd":25,"githubUrl":"https://github.com/appsmithorg/appsmith/blob/8cd9021c24cdbea1c3c12c966073708e83db60c2/app/client/packages/design-system/ads/src/FormControl/FormControl.context.ts#L1-L25","documentation":"Intended to fire from useFormControlContext() in the Appsmith Design System when a FormControl compound component (e.g. FormLabel, FormHelperText, FormInput) is rendered without a FormControl ancestor. IMPORTANT DEFECT grounded in the source: FormControlContext is created with createContext({...}) passing a non-null default object ({isRequired:false,isDisabled:false,size:\"sm\"}), so useContext always returns a truthy value and the `if (!context)` guard is DEAD CODE — this error can never actually be thrown at runtime as written. The intent is real; the implementation does not achieve it.","triggerScenarios":"Render a FormControl.* subcomponent outside <FormControl>. Intended trigger only — in practice the non-null default suppresses the throw, so the component silently renders with default context values instead.","commonSituations":"Copying a Form subcomponent into a story or test without the FormControl wrapper; refactoring a form and hoisting a label out of FormControl; using the design system primitives standalone. Developer expects a clear error but gets silent default sizing.","solutions":["Always render compound components inside <FormControl> so context is provided explicitly.","Fix the underlying defect: pass null as the createContext default (createContext<Pick<...> | null>(null)) so the guard becomes live, matching the Sidebar pattern.","If fixing the library, audit all consumers once the throw becomes reachable — currently any out-of-tree usage has been silently passing.","Add a unit test rendering a compound component without a provider to lock in the intended behavior."],"exampleFix":"// before (dead guard)\nexport const FormControlContext = createContext({ isRequired:false, isDisabled:false, size:\"sm\" });\nexport const useFormControlContext = () => {\n  const context = useContext(FormControlContext);\n  if (!context) { throw new Error(...); }  // unreachable\n  return context;\n};\n\n// after (live guard)\nexport const FormControlContext = createContext<Pick<FormControlProps,\"isRequired\"|\"isDisabled\"|\"size\"> | null>(null);\nexport const useFormControlContext = () => {\n  const context = useContext(FormControlContext);\n  if (!context) { throw new Error(\"FormControl compound components cannot be rendered outside the FormControl component\"); }\n  return context;\n};","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// NOTE: current default is non-null so this never throws; fix the lib first.\n// After fix (createContext<T|null>(null)):\nfunction hasFormControlContext<T>(ctx: T | null): ctx is T { return ctx !== null; }","tryCatchPattern":"// Wrap risky rendering; today this is dead code, so add the provider regardless.\ntry { return renderCompound(); } catch (e) {\n  if (/FormControl compound components/i.test(e.message)) return <FormControl>{renderCompound()}</FormControl>;\n  throw e;\n}","preventionTips":["Always nest compound components in <FormControl>.","Fix the createContext default to null so the guard is live and surfaces misuse.","Add a story/test that renders a compound outside the provider to verify the throw."],"tags":["react","design-system","context","compound-components","dead-code"],"backgroundTag":null,"analyzedSha":"8cd9021c24cdbea1c3c12c966073708e83db60c2","analyzedAt":"2026-08-12T22:14:19.293Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}