Skip to content

A uiSchema template override that is a React element or an unregistered name reaches React instead of falling back #5432

Open

Description

@heath-freenome

Prerequisites

  • I have searched the existing issues
  • I understand that providing a SSCCE example is tremendously useful to the maintainers
  • I have read the documentation

What theme are you using?

core

Version

7.x (v7 branch)

Current Behavior

getTemplate() in packages/utils/src/getTemplate.ts resolves a template override from the uiSchema (ui:FieldTemplate, ui:ArrayFieldTemplate, ui:ObjectFieldTemplate and the rest) two ways: a string that names a registered template, or anything else that is truthy:

if (Object.hasOwn(uiOptions, name) && typeof uiOptions[name] === 'string' && Object.hasOwn(templates, uiOptions[name])) {
  return (templates as any)[key];
}
return ((uiOptions as any)[name] as TemplatesType<T, S, F>[Name]) || templates[name];

The fallback returns the override without checking that it is a component, so two mistakes reach React. getTemplates() resolves each override through getTemplate(), so it returns the same values. Measured on v7:

uiSchema getTemplate('FieldTemplate', …) returns at render
'ui:FieldTemplate': MyTemplate MyTemplate renders
'ui:FieldTemplate': 'FieldTemplate' (registered) the registered template renders
'ui:FieldTemplate': <MyTemplate /> (an element) the element React throws "Element type is invalid"
'ui:FieldTemplate': 'Fieldtemplate' (a typo) the string 'Fieldtemplate' rendered as an unknown <fieldtemplate> DOM element, with the template's props as attributes

The typo is the worse of the two: nothing throws, the field's markup is replaced by an unknown element, and the only signal is whatever React logs about unknown props.

Expected Behavior

Fall back to the registry's template when the override is neither a component nor the name of a registered template, and warn through logOnce(), the same way #5363 treats an unusable ui:field and a LayoutGridField cell's render.

Steps To Reproduce

import { getTemplate } from '@rjsf/utils';

const FieldTemplate = () => null;
const registry = { templates: { FieldTemplate, ButtonTemplates: {} } } as any;

getTemplate('FieldTemplate', registry, { FieldTemplate: <FieldTemplate /> } as any); // the element
getTemplate('FieldTemplate', registry, { FieldTemplate: 'Fieldtemplate' } as any); // 'Fieldtemplate'

Or in a form: uiSchema={{ 'ui:FieldTemplate': 'Fieldtemplate' }} replaces the root field's template with an unknown DOM element.

Environment

- OS: macOS (Darwin 25.6.0)
- Node: 24.21.0
- npm: pnpm 12.8.1

Anything else?

#5363 adds isComponentType() to @rjsf/utils, which accepts a function or a memo()/forwardRef()/lazy() component and rejects a React element. The fallback above can use it. ButtonTemplates, the one template that is a map of components rather than a component, already returns early from getTemplate(), so the check doesn't need a special case for it.

Found in review of the #5363 fix and left out of it to keep that change scoped to ui:field, ui:widget and render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions