RJSF fully supports Typescript.
The types and functions exported by @rjsf/utils
are fully typed (as needed) using one or more of the following 3 optional generics:
T = any
: This represents the type of theformData
and defaults toany
.S extends StrictRJSFSchema = RJSFSchema
: This represents the type of theschema
and extends theStrictRJSFSchema
type and defaults to theRJSFSchema
type.F extends FormContextType = any
: This represents the type of theformContext
, extends theFormContextType
type and defaults toany
.
Every other library in the @rjsf/*
ecosystem use these same generics in their functions and React component definitions.
For instance, in the @rjsf/core
library the definitions of the Form
component and the withTheme()
and getDefaultRegistry()
functions are as follows:
export default class Form<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
> extends Component<FormProps<T, S, F>, FormState<T, S, F>> {
// ... class implementation
}
export default function withTheme<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(themeProps: ThemeProps<T, S, F>) {
// ... function implementation
}
export default function getDefaultRegistry<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(): Omit<Registry<T, S, F>, "schemaUtils"> {
// ... function implementation
}
Out of the box, the defaults for these generics will work for all use-cases.
Providing custom types for any of these generics may be useful for situations where the caller is working with typed formData
, schema
or formContext
props, Typescript is complaining and type casting isn't allowed.
The generic T
is used to represent the type of the formData
property passed into Form
.
If you are working with a simple, unchanging JSON Schema and you have defined a type for the formData
you are working with, you can override this generic as follows:
import { RJSFSchema } from "@rjsf/utils";
import { validator } from "@rjsf/validator-ajv8";
import { Form } from "@rjsf/core";
interface FormData {
foo?: string;
bar?: number;
};
const schema: RJSFSchema = {
type: "object",
properties: {
"foo": { type: "string" },
"bar": { type: "number" }
}
};
const formData: FormData = {};
render((
<Form<FormData> schema={schema} validator={validator} formData={formData} />
), document.getElementById("app"));
The generic S
is used to represent the type of the schema
property passed into Form
.
If you are using something like the Ajv utility types for schemas typing system, you can override this generic as follows:
import { JSONSchemaType } from "ajv";
import { RJSFSchema } from "@rjsf/utils";
import { validator } from "@rjsf/validator-ajv8";
import { Form } from "@rjsf/core";
interface FormData {
foo?: string;
bar?: number;
};
type MySchema = JSONSchemaType<FormData>;
const schema: MySchema = {
type: "object",
properties: {
"foo": { type: "string" },
"bar": { type: "number" }
}
};
render((
<Form<any, MySchema> schema={schema} validator={validator} />
), document.getElementById("app"));
// Alternatively since you have the type, you could also use this
// render((
// <Form<FormData, MySchema> schema={schema} validator={validator} />
//), document.getElementById("app"));
NOTE: using this
Ajv typing system
has not been tested extensively with RJSF, so use carefully
The generic F
is used to represent the type of the formContext
property passed into Form
.
If you have a type for this data, you can override this generic as follows:
import { RJSFSchema } from "@rjsf/utils";
import { validator } from "@rjsf/validator-ajv8";
import { Form } from "@rjsf/core";
interface FormContext {
myCustomWidgetData: object;
};
const schema: RJSFSchema = {
type: "object",
properties: {
"foo": { type: "string" },
"bar": { type: "number" }
}
};
const formContext: FormContext = {
myCustomWidgetData: {
enableCustomFeature: true,
}
};
render((
<Form<any, RJSFSchema, FormContext> schema={schema} validator={validator} formContext={formContext} />
), document.getElementById("app"));
As shown in previous examples, overriding the default Form
from @rjsf/core
is pretty straight forward.
Using the withTheme()
function is just as easy:
import { RJSFSchema } from "@rjsf/utils";
import { validator } from "@rjsf/validator-ajv8";
import { withTheme, ThemeProps } from '@rjsf/core';
interface FormData {
foo?: string;
bar?: number;
};
type MySchema = JSONSchemaType<FormData>;
const schema: MySchema = {
type: "object",
properties: {
"foo": { type: "string" },
"bar": { type: "number" }
}
};
interface FormContext {
myCustomWidgetData: object;
};
const theme: ThemeProps<FormData, MySchema, FormContext> = { widgets: {test: () => (<div>test</div>) } };
const ThemedForm = withTheme<FormData, MySchema, FormContext>(theme);
const Demo = () => (
<ThemedForm schema={schema} uiSchema={uiSchema} validator={validator} />
);
Since all the other themes in RJSF are extensions of @rjsf/core
, overriding parts of these themes with custom generics is a little different.
The exported Theme
and Form
from any of the themes have been created using the generic defaults, and as a result, do not take generics themselves.
In order to override generics, special generateForm()
and generateTheme()
functions are exported for your use.
If you are doing something like the following to create a new theme based on @rjsf/mui
to extend one or more templates
:
import React from "react";
import { WidgetProps } from "@rjsf/utils";
import { ThemeProps, withTheme } from "@rjsf/core";
import { validator } from "@rjsf/validator-ajv8";
import { Theme } from "@rjsf/mui";
const OldBaseInputTemplate = Theme.templates.BaseInputTemplate;
// Force the underlying `TextField` component to always use size="small"
function MyBaseInputTemplate(props: WidgetProps) {
return <OldBaseInputTemplate {...props} size="small" />;
}
const myTheme: ThemeProps = {
...Theme,
templates: {
...Theme.templates,
BaseInputTemplate: MyBaseInputTemplate,
},
};
const ThemedForm = withTheme(myTheme);
const Demo = () => (
<ThemedForm schema={schema} uiSchema={uiSchema} validator={validator} />
);
Then you would use the new generateTheme()
and generateForm()
functions as follows:
import React from "react";
import { WidgetProps } from "@rjsf/utils";
import { ThemeProps, withTheme } from "@rjsf/core";
import { validator } from "@rjsf/validator-ajv8";
import { generateTheme } from "@rjsf/mui";
interface FormData {
foo?: string;
bar?: number;
};
type MySchema = JSONSchemaType<FormData>;
const schema: MySchema = {
type: "object",
properties: {
"foo": { type: "string" },
"bar": { type: "number" }
}
};
interface FormContext {
myCustomWidgetData: object;
};
const Theme: ThemeProps<FormData, MySchema, FormContext> = generateTheme<FormData, MySchema, FormContext>();
const OldBaseInputTemplate = Theme.templates.BaseInputTemplate;
// Force the underlying `TextField` component to always use size="small"
function MyBaseInputTemplate(props: WidgetProps<FormData, MySchema, FormContext>) {
return <OldBaseInputTemplate {...props} size="small" />;
}
const myTheme: ThemeProps<FormData, MySchema, FormContext> = {
...Theme,
templates: {
...Theme.templates,
BaseInputTemplate: MyBaseInputTemplate,
},
};
const ThemedForm = withTheme<FormData, MySchema, FormContext>(myTheme);
// You could also do since they are effectively the same:
// const ThemedForm = generateForm<FormData, MySchema, FormContext>(myTheme);
const Demo = () => (
<ThemedForm schema={schema} uiSchema={uiSchema} validator={validator} />
);
NOTE: The same approach works for extending
widgets
andfields
as well.