Skip to content

React integration

The @ostack.tech/kform-react package provides middleware to interact with KForm from React.

<Form>

KForm-React forms can be declared via the <Form> component. This component expects, amongst other optional properties, a mandatory schema containing the schema of the form, which is used to instantiate a FormManager used to manage the whole form:

import { Form } from "@ostack.tech/kform-react";
import { BusTripFormSchema } from "bus-trip-form-shared";

export function BusTripForm() {
    return (
        <Form
            schema={BusTripFormSchema.get()}
            initialValue={/* … */}
            externalContexts={/* … */}
            validationMode={/* … */}
            onSubmit={async (value, warnings) => {
                const result = await submitBusTripForm(value, warnings);
                return result.success ? result.redirectUrl : result.issues;
            }}
            onSuccessfulSubmit={(redirectUrl) => {
                window.location.href = redirectUrl;
            }}
        >
            <button type="submit">Submit</button>
            {/* … */}
        </Form>
    );
}

The <Form> component supports the following main properties:

  • schema: The schema of the form. Typically defined in a shared Kotlin multiplatform module and exposed to the JavaScript world via @JsExport.
  • initialValue: The initial value of the form, as expected by the FormManager’s constructor.
  • externalContexts: External contexts used by the form’s validations or computed values. Expects an object mapping context names to their values.

    The value of this property may be dynamically updated. The component uses the form manager’s setExternalContext/removeExternalContext API to update the external contexts as appropriate.

  • validationMode: The mode of validation used by the form manager. See the Validation modes section for more details.

    The value of this property may be dynamically updated. The component uses the form manager’s setValidationMode API to update the validation mode as appropriate.

  • confirmUnloadWhenDirty: When true (the default in production), the browser will ask the user for confirmation when attempting to unload the form (e.g. closing the page) while the form is dirty.

  • onSubmit: Function called during submission when the form value to submit is locally valid (no validation errors were found) or if validateOnSubmit is set to false. This function may (possibly asynchronously) return two types of values, or throw an error:

    • Function returns external validation issues: the submission is considered invalid (even if all returned issues are warnings). Returned issues that aren’t already in the form manager are added to it via addExternalIssues. onInvalidSubmit is subsequently called with the returned issues.
    • Function returns any other value: the submission is considered successful. onSuccessfulSubmit will be called with the returned value after setting the form as pristine (unless setPristineOnSuccessfulSubmit is set to false).
    • Function throws: the submission is considered to have failed. onFailedSubmit will be called with the thrown error.
  • onInvalidSubmit: Function called during submission when the form value to submit is locally invalid (local validation errors were found), or after onSubmit if it returns external validation issues.

  • onSuccessfulSubmit: Function called after a successful submission with the result of onSubmit (when this result isn’t a list of issues).

    Unless setPristineOnSuccessfulSubmit is set to false, the form will be pristine by the time this function runs, meaning that redirections that cause the form to be unloaded should happen within this function (so that the form unload confirmation isn’t triggered).

  • onFailedSubmit: Function called after a submission that resulted in an error.

  • onReset: Function called when resetting the form, before the form is actually reset. Preventing the default behaviour on the received event (via event.preventDefault()) will prevent the form from being reset.
  • setTouchedOnSubmit: Whether to mark the whole form as touched before submitting. Defaults to true.
  • validateOnSubmit: Whether to validate the form before submitting. Defaults to true. When set to false, onSubmit is always called and onInvalidSubmit is only called if onSubmit returns external validation issues.
  • setPristineOnSuccessfulSubmit: Whether to set the whole form as pristine after a successful submission, but before invoking onSuccessfulSubmit. Defaults to true.
  • convertExternalIssuesTableRowIndicesToIds: KForm table rows have unique non-sequential identifiers. Depending on how you choose to serialise your form, these identifiers might be discarded. In such a scenario, upon deserialisation on the server, the identifiers will typically become sequential (i.e. index-based). This means that, if issues were found server-side within a table, the paths will contain identifiers not matching the client-side ones.

    By setting this property to true, the component will convert table row indices into their local client-side identifiers before processing the external issues returned by onSubmit.

useFormManager

The form’s FormManager instance is exposed via the useFormManager hook, as long as the hook is called within a <Form> component:

const formManager = useFormManager();

useController

Controllers leverage the form manager to provide simple-to-use React APIs for accessing a form’s value and its state.

The useController hook is the foundation of all hooks which expose controllers:

const controller = useController(path, options?);

The provided path is resolved against the path currently in scope and must only contain identifiers and an optional trailing recursive wildcard fragment (/**). When a trailing recursive wildcard is provided, it indicates that the controller should also react to changes in descendants.

The hook receives, as its second argument, an optional object supporting the following properties:

  • formManager: The form manager instance to use. Defaults to the one in scope.
  • enabled: a boolean indicating whether the controller should be enabled.
  • onInitialized(state): callback function called with the controller’s state when it is initialised.
  • onUninitialized(state): callback function called whenever the controller is uninitialised.
  • onFormManagerEvent(event, state): callback function called whenever the form manager emits an event matching the controller’s path.
  • onValueChange(event, state): callback function called whenever the controller’s value changes.
  • onValidationStatusChange(event, state): callback function called whenever the controller’s validation status changes.
  • onDisplayStatusChange(event, state): callback function called whenever the controller’s display status changes.
  • onDirtyStatusChange(event, state): callback function called whenever the controller’s dirty status changes.
  • onTouchedStatusChange(event, state): callback function called whenever the controller’s touched status changes.

Whereas the returned controller object contains the following functions:

  • getState(): Returns the current state of controller. Should not be called during render.
  • subscribe(selector, listener, options?): Function used to subscribe to changes in the controller’s state. Returns an unsubscribe function.
  • useState(selector?, options?): Hook providing access to the controller’s state at render-time.
  • useFormManager(): Hook which returns the form manager being used by the controller.
  • useSchema(): Hook which returns the schema of the value being controlled.
  • usePath(): Hook which returns the path of the value being controlled by this controller. This path will not contain any provided recursive wildcards.
  • useSchemaPath(): Hook which returns the schema path of the value being controlled.
  • useObservingDescendants(): Hook which returns whether the controller is currently observing descendants.
  • useInitialized(): Hook which returns whether the controller has been initialised.
  • useExists(): Hook which returns whether a value exists at the path being controlled, or undefined when the controller is not initialised.
  • useValue(): Hook which returns the form value being controlled, or undefined when the controller is not initialised.

    Note that, when observing descendants, this hook will cause a component to rerender when a descendant is changed, even if the identity of the value hasn’t changed.

  • useDirty(): Hook which returns whether the value being controlled is dirty, or undefined when the controller is not initialised.

  • useTouched(): Hook which returns whether the value being controlled is touched, or undefined when the controller is not initialised.
  • useIssues(): Hook which returns the issues of the value being controlled, or undefined when the controller is not initialised.
  • useValidationStatus(): Hook which returns the validation status of the value being controlled, or undefined when the controller is not initialised.
  • useDisplayStatus(): Hook which returns the display status of the value being controlled, or undefined when the controller is not initialised.
  • get(callback)/get(path, callback): FormManager.get with an optional path relative to the controller’s.
  • getClone(path?): FormManager.getClone with an optional path relative to the controller’s.
  • set(value)/set(path, value): FormManager.set with an optional path relative to the controller’s.
  • reset(path?): FormManager.reset with an optional path relative to the controller’s.
  • remove(path?): FormManager.remove with an optional path relative to the controller’s.
  • validate(path?): FormManager.validate with an optional path relative to the controller’s.
  • setDirty(path?): FormManager.setDirty with an optional path relative to the controller’s.
  • setPristine(path?): FormManager.setPristine with an optional path relative to the controller’s.
  • setTouched(path?): FormManager.setTouched with an optional path relative to the controller’s.
  • setUntouched(path?): FormManager.setUntouched with an optional path relative to the controller’s.

The controller’s state is a plain object containing the following properties:

  • formManager: Form manager being used by the controller.
  • schema: Schema of the form value being controlled.
  • path: Path of the form value being controlled.
  • schemaPath: Schema path of the form value being controlled.
  • observingDescendants: Whether the controller is observing descendants.
  • initialized: Whether the controller has been initialised.
  • exists: Whether the form value being controlled exists.
  • value: Form value.
  • dirty: Whether the form value being controlled is dirty.
  • touched: Whether the form value being controlled has been touched.
  • issues: Validation issues associated with the form value being controlled.
  • validationStatus: Validation status of the form value being controlled. One of "unvalidated", "validating", "validated", or "validatedExceptionally".
  • displayStatus: Display status of the form value being controlled. One of "valid", "error", or "warning".

useFormController

The form’s own controller (which controls the root value of the form) can be accessed via the useFormController() hook:

const controller = useFormController();

It returns a controller object which extends a useController controller with (amonst others) a submit function:

  • submit(options?): Function which can be used to programmatically submit the form.

useFormatter

The useFormatter hook extends useController with formatting capabilities.

const controller = useFormatter(path, options?);

Besides all options accepted by useController, the options object also accepts the following properties:

  • setFormattedValue(formattedValue, state): Function used to set the formatted value. A useState dispatcher can be used to save the output value within React state.

    The passed value results from calling format on the form manager value. If format is not defined, then the form manager value is passed directly.

  • format(value, state): Format function that transforms the form manager value into a value to be output. The passed value is undefined when the form manager value does not exist.

The returned controller object extends the useController controller with the following function:

  • useFormattedValue(): Hook which returns whether the formatted value.

The controller’s state extends the useController controller’s state with the following property:

  • formattedValue: Value after being formatted.

useFormattedValue

The useFormattedValue hook is a convenience wrapper around useFormatter, providing the formatted value directly as state:

const [formattedValue, controller] = useFormattedValue(path, options?);

Usage of this hook is recommended whenever there is a need to access a value of the form at render-time.

<FormattedValue>

The <FormattedValue> component is another convenience wrapper around useFormatter for displaying formatted values.

<FormattedValue path={path} format={/*…*/} />
<FormattedValue path={path} format={/*…*/}>
    {(state) => {
        /*…*/
    }}
</FormattedValue>

useInput

The useInput hook extends useFormatter for integration with form controls such as <input>, so that values are written to the form manager as the user interacts with the form control, and vice versa:

const controller = useInput(path, options?);

It accepts all options accepted by useController, as well as the following properties:

  • setFormattedValue(formattedValue, state, element): Function used to set the formatted value in the form control. The passed value results from calling format on the form manager value. If format is not defined, then the form manager value is passed directly.

    By default, this function is capable of setting the values of <input>, <select>, and <textarea> elements.

  • format(value, state, element): Function that formats the form manager value into a value to be set in the form control. The passed value is undefined when the form manager value does not exist.

  • parse(value, state, element): Function used to parse the form control value into a value to be set in the form manager.

The returned controller extends the useFormatter controller with the following extra property:

  • inputProps: Object containing the properties to be passed to the form control:

    • name: Name of the form control.
    • disabled: Whether the form control should be disabled. This is true while the form is being submitted or when the value does not exist.
    • readOnly: Whether the form control should be read-only. This is true when the schema associated with this control is computed.
    • ref: Reference to the form control.
    • onChange: Function to run whenever the value of the form control changes. This function can receive either a change event with a standard HTML <input>, <select>, or <textarea> as target or a value directly.

      This function sets the new value in the form manager and marks the value as “dirty”.

    • onBlur: Function to run whenever the input element is blurred. This function marks the form value as “touched”.

The following example shows how to use the useInput hook to create a custom text input component:

Example text input component
export function TextInput({ path, className, disabled, readOnly, ref, ...otherProps }) {
    const { useSchema, useInitialized, useIssues, useDisplayStatus, inputProps } = useInput(path);
    const { restrictions } = useSchema().typeInfo;
    const initialized = useInitialized();
    const displayStatus = useDisplayStatus();

    return (
        <input
            className={classNames(displayStatus, className)}
            name={inputProps.name}
            disabled={inputProps.disabled || !initialized || disabled}
            readOnly={inputProps.readOnly || readOnly}
            required={restrictions.required === true}
            minLength={restrictions.minLength}
            maxLength={restrictions.maxLength}
            pattern={restrictions.pattern}
            aria-invalid={displayStatus === "error"}
            onChange={combineEventHandlers(inputProps.onChange, onChange)}
            onBlur={combineEventHandlers(inputProps.onBlur, onBlur)}
            ref={combineRefs(inputProps.ref, ref)}
            {...otherProps}
        />
    );
}

useFileInput

The useFileInput hook extends useInput for integration with values with a FileSchema:

const controller = useFileInput(path, options?);

An error is thrown if the path does not point to a value with a FileSchema.

This hook accepts the same options as useInput, but instead of format and parse, it instead accepts properties that deal with JavaScript File objects (abstracting away the usage of KForm Files):

  • formatFromJsFile(fileValue, state, element): Function that formats a JavaScript File into a value to be set in the form control.
  • parseToJsFile(formattedValue, state, element): Function used to parse the form control value into a JavaScript File to be set in the form manager.

useListableInput

The useListableInput hook extends useInput for integration with values with a “listable” schema (ListSchema or TableSchema):

const controller = useListableInput(path, options?);

An error is thrown if the path does not point to a value with a “listable” schema.

This hook accepts the same options as useInput, but instead of format and parse, it instead accepts properties that deal with arrays (abstracting away the usage of the different listable types):

  • formatFromArray(arrayValue, state, element): Function that formats an array into a value to be set in the form control.
  • parseToArray(formattedValue, state, element): Function used to parse the form control value into an array to be set in the form manager.

useNumericInput

The useNumericInput hook extends useInput for integration with values with a “numeric” schema (ByteSchema, CharSchema, ShortSchema, IntSchema, LongSchema, FloatSchema, DoubleSchema, BigIntegerSchema, BigDecimalSchema, or StringSchema):

const controller = useNumericInput(path, options?);

An error is thrown if the path does not point to a value with a “numeric” schema.

This hook accepts the same options as useInput, but instead of format and parse, it instead accepts properties that deal with numeric strings (abstracting away the usage of the different numeric types):

  • formatFromString(stringValue, state, element): Function that formats a numeric string into a value to be set in the form control.
  • parseToString(formattedValue, state, element): Function used to parse the form control value into a numeric string to be set in the form manager.

useTemporalInput

The useTemporalInput hook extends useInput for integration with values with a “temporal” schema (IntantSchema, LocalDateSchema, or LocalDateTimeSchema):

const controller = useTemporalInput(path, options?);

An error is thrown if the path does not point to a value with a “temporal” schema.

This hook accepts the same options as useInput, but instead of format and parse, it instead accepts properties that deal with ISO 8601 strings (abstracting away the usage of the different temporal types):

  • formatFromString(stringValue, state, element): Function that formats an ISO 8601 string into a value to be set in the form control.
  • parseToString(formattedValue, state, element): Function used to parse the form control value into an ISO 8601 string to be set in the form manager.

<CurrentPath>

The <CurrentPath> component makes the current base path for its children:

<CurrentPath path="/tripDates">
    <TextInput path="departure" /> {/* Resolved path is "/tripDates/departure" */}
    <TextInput path="return" /> {/* Resolved path is "/tripDates/return" */}
</CurrentPath>

Children of <CurrentPath> that use KForm-React hooks or components taking paths, will have their path resolved against the provided path.

useCurrentPath

The useCurrentPath hook provides access to the path currently in scope.

const currentPath = useCurrentPath();

The path currently in scope can be specified via the <CurrentPath> component.

useResolvedPath

The useResolvedPath hook resolves the provided path against the path currently in scope:

const resolvedPath = useResolvedPath(path);

The returned path instance is stable across renders, so it can be safely used in dependency arrays of React hooks.

useIssuesTracker

The useIssuesTracker hook can be used to keep track of all validation issues with paths matching the provided path:

const tracker = useIssuesTracker(path, options?);

The path defaults to ./** (all descendants of the current path). Supported options are:

  • formManager: The form manager instance to use. Defaults to the one in scope.
  • enabled: Whether to enable validation issues tracking.
  • issuesOrderCompareFn(path1, path2): Function which may be provided to specify the order of the resulting issues.

    It receives the paths of the issues being compared and should return a number in typical Array.prototype.sort#compareFn fashion to determine the order of the issues.

    The default ordering is based on the order in which fields were declared within the form schema. If this function is provided, and 0 is returned, then the default schema-based ordering will be used for disambiguation. Note that this function should not change on every render, as any change to this function will cause the tracker to “restart”.

The hook returns an object with the following properties:

  • initialized: Whether the tracker has been initialised.
  • info: Array containing validation information about the values with issues. Each element is an object with the following properties:
    • path: Path of the value with issues.
    • issues: Issues of the value (the array is guaranteed to not be empty).
    • localDisplayStatus: Local display status of the value (ignoring issues of descendants).
  • errors: Total number of errors.
  • warnings: Total number of warnings.

useSubscription

The useSubscription allows one to subscribe to all form manager events with paths matching the provided path and to run the provided callback for each event:

useSubscription(path, callback, options?);

The following options are supported:

  • formManager: The form manager instance to use. Defaults to the one in scope.
  • enabled: Whether to enable the subscription.
  • onSubscribe(): Function called after the subscription completes, but before any event is emitted.
  • onUnsubscribe(): Function called after unsubscribing.

Changes to any of the callbacks do not cause resubscriptions.

useSubmitting

The useSubmitting hook returns whether the form is currently being submitted.

const submitting = useSubmitting();

useResetting

The useResetting hook returns whether the form is currently being reset.

const resetting = useResetting();

useAutoValidationStatus

The useAutoValidationStatus hook returns the current status of the automatic validation daemon.

const status = useAutoValidationStatus();

The automatic validation status can be one of:

  • inactive: Automatic validations are inactive (validationMode is set to "manual").
  • activeIdle: Automatic validations are active but idle (validationMode is set to "auto" but there is nothing to validate).
  • activeRunning: Automatic validations are active and running (validationMode is set to "auto" and values are currently being validated).

setKFormLogLevel

The setKFormLogLevel can be used to aid debugging by setting the log level used by the core KForm library.

setKFormLogLevel(level);

The log level can be set to one of: "trace", "debug", "info", "warn", "error", and "off". The default level is "info".

During development (i.e. whenever process.env.NODE_ENV is not set to "production"), the setKFormLogLevel function is available as a global variable. This allows developers to dynamically change the log level by opening the browser’s console and typing, for example, setKFormLogLevel("debug"). Upon doing so, log messages should start appearing in the console as one interacts with the form.