Creating the form store
By calling the useCreateFormStore hook with a configuration object we create a new formStore
instance that manages the state of a single form.
The <Form> component renders the form based on the formStore
instance we provide to it.
We have access to the formStore
state from each of our form components (that we provided to the FormComponentMappingsProvider) via hooks like useParentFormStore
. The instance of the formStore
that the component accesses will be based on which form renders the given component. In this way we create dynamic reusable components that can fit in any form.
Configuring the form store
Property type: CreateStoreConfiguration
The configuration we provide to the useCreateFormStore hook will determine how the form and the state store will behave.
We have 2 types of configurations:
- Single step form (Property type: CreateStoreSingleStepConfiguration)
- Multi step form (Property type: CreateStoreMultiStepConfiguration)
Components
Property path:
configuration
> steps
> components
Property type: FormComponentConfiguration[]
The most important configuration in any form is the components of the form. These components will be rendered one after another by the <Form> component (except for wrapper components). There are 3 types of components that can be defined here:
- Field components: these hold user input values
- UI components: these can be anything that don't hold user inputs (ex. buttons)
- Wrapper components: these wrap around other components
Accessing the form store in functions
Many of the configuration properties are functions that will be called at specific times. These functions will have access to the formStore
instance that is created by the useCreateFormStore hook.
In some cases you need to supply an array of dependencies
to ensure that the function is called when its dependencies from the formStore
change.
Common component properties
Most of the configurable properties are common between the 3 types of components.
UI Components do not have specific properties, they only have the following common properties.
type
Property path:
configuration
> steps
> components
> type
Property type: "field" | "ui" | "wrapper"
Determines what type of form component the configuration will be for.
formComponentMappingKey
Property path:
configuration
> steps
> components
> formComponentMappingKey
Property type: string
The mapping key defined in the form component mappings (it will map to a React component).
name
Property path:
configuration
> steps
> name
Property type: string
This will be the identifier of the component in the form. For field
type components this is required because we want them to have specific keys when submitting data to our server or to have dynamic logic tied to their values.
For ui
and wrapper
type components usually it's not needed to define their name
(it will be auto generated containing a unique nanoid if not defined), unless if we want to implement some kind of dynamic logic tied to their componentParams.
componentParams
Property path:
configuration
> steps
> components
> componentParams
Learn how to access the form store in any configuration property that is a function.
This is the equivalent of props in traditional React components. The component will be able to access its params via the useFormComponentParams
hook.
This can also be defined in a dynamic way by providing it an object with a value
function that returns the parameters and an optional dependencies
function that will trigger the value
function when the dependencies change.
We can also provide an async value
function, which will set the corresponding isLoading
and loadingError
states for the componentParams in the store.
If providing an async value
function, we can also provide a staticPart
object, which will be merged with the resolved value of the async function.
In case of async value
function, ou should supply the abortController
to the request handler (like fetch
), because in case of race conditions abort
will be called when necessary.
It's recommended to ensure the type safety of the formComponentMappingKey
and componentParams
properties. Learn more in the
Typescript section.
shouldShowOnlyIf
Property path:
configuration
> steps
> components
> shouldShowOnlyIf
Learn how to access the form store in any configuration property that is a function.
This configuration allows for an easy way to define logic for showing or hiding the component.
If a generic object is supplied then the keys of the object represent the names of the fields, and the component will show only if all the field values are equal to the ones defined in the object.
If an object is supplied with a value
key that is a function, then the component will show only if the value
function returns true
. This function will run each time one of the values from the dependencies
array change.
If the component is a field component and it's not showing, it will get excluded from the return value of methods which return the fields' values (ex. getStepFieldValues, getAllFieldValues).
shouldShowOnlyIfMediaQueryMatches
Property path:
configuration
> steps
> components
>
shouldShowOnlyIfMediaQueryMatches
Property type: string
If this is defined, the field will only render if the media query is matched. For example @media (min-width: 600px)
will only render the field if the screen is wider than 600px.
If the component is a field component and it's not matching the media query, it will get excluded from the return value of methods which return the fields' values (ex. getStepFieldValues, getAllFieldValues).
shouldShowOnlyOnScreenSize
Property path:
configuration
> steps
> components
> shouldShowOnlyOnScreenSize
Property type: { min: number | null; max: number | null }
If this is defined, the field will only render if the screen size is matching the min
and max
. This is an easier to read alternative to mediaQuery
. For example { min: null, max: 500 }
will render the field if the screen width is below 500px. (When min: null
, it means 0px
. When max: null
, it means Infinity
)
If the component is a field component and it's not matching the screen size, it will get excluded from the return value of methods which return the fields' values (ex. getStepFieldValues, getAllFieldValues).
Field component specific properties
These are the only components that hold user input values and validation errors.
In addition to the common properties for the different component types, Field components have these additional configurable properties:
defaultValue
Property path:
configuration
> steps
> components
> defaultValue
Property type: any
When the form is created via the useCreateFormStore, this will be the value
in the field's state. Additionally, after calling the resetting methods this is what the value
will be set to in the field's state.
shouldKeepValueEvenIfHidden
Property path:
configuration
> steps
> components
> shouldKeepValueEvenIfHidden
Property type: boolean
If the component is hidden because of shouldShowOnlyIf, shouldShowOnlyIfMediaQueryMatches or shouldShowOnlyOnScreenSize, then the value of the field will reset to its initial value.
If this is set to true
, the value will not be reset.
shouldIncludeInValuesEvenIfNotShowing
Property path:
configuration
> steps
> components
>
shouldIncludeInValuesEvenIfNotShowing
Property type: boolean
If the component is hidden because of shouldShowOnlyIf, shouldShowOnlyIfMediaQueryMatches or shouldShowOnlyOnScreenSize, then the value of the field will be excluded from the return value of methods which return the fields' values (ex. getStepFieldValues, getAllFieldValues).
If this is set to true
, the value will be included in the return value of these methods even if the field is hidden.
validationRules
Property path:
configuration
> steps
> components
> validationRules
Property type: FieldValidationRules
The configurable validation rules are:
- required: If the field's value is
"" || null || undefined
(or if the field has nestedArrayComponents then[]
will also yield error), a validation error will be attached to the field. - maxLength: Useful for
string
andarray
type fields. If the field's value's length is smaller than the value defined here, a validation error will be attached to the field. - minLength: Useful for
string
andarray
type fields. If the field's value's length is bigger than the value defined here, a validation error will be attached to the field. - exactLength: Useful for
string
andarray
type fields. If the field's value's length is not equal to the value defined here, a validation error will be attached to the field. - minValue: Useful for
number
type inputs. If the field's value is bigger than the value defined here, a validation error will be attached to the field. - maxValue: Useful for
number
type inputs. If the field's value is smaller than the value defined here, a validation error will be attached to the field. - minDate: Useful for
date
type fields. If the field's value is before the date value defined here, a validation error will be attached to the field. - maxDate: Useful for
date
type fields. If the field's value is after the date value defined here, a validation error will be attached to the field. - email: Useful for
string
type fields. If the field's value is not a valid email, a validation error will be attached to the field. - url: Useful for
string
type fields. If the field's value is not a valid URL, a validation error will be attached to the field. - onlyStrongPasswordCharacters: Useful for
string
type fields. If the field's value does not contain at least one uppercase letter, one lowercase letter, one number and one special character, a validation error will be attached to the field. - phoneNumber: Useful for
string
type fields. If the field's value is not a valid phone number, a validation error will be attached to the field. It uses the phone package under the hood and theoptions
object can be used to define the country code, validate the mobile prefix and strict detection. - pattern: Useful for
string
type fields. If the field's value is not matching the pattern, a validation error will be attached to the field. - matchAnotherField: Useful for confirmation fields that are dependent on another field. If the field's value is not matching the value of the field whose name is the value of this, a validation error will be attached to the field.
- mustBeEqualTo: Useful for confirmation fields that are not dependent on another field (for example to confirm deletion write "DELETE" or for enforcing the checking of a checkbox). If the field's value is not matching this value, a validation error will be attached to the field.
- customValidation: If this function returns a
string
, that will be thevalidationError
attached to the field. Forobject
orobject[]
type values, only this can do the validation. - customAsyncValidation: Same as
customValidation
but only for async validations. This will only trigger if all the other sync validation rules passed.
The fields' validationError are an array of strings that the message
functions of the rules that did not pass validation return. Additionally, you can define the priority
for each validation rule. The validation errors will be sorted by this priority.
For better user experience you can fine tune the debounceDurationInMs
, it's especially recommended for the async validation (asyncDebounceDurationInMs
). This can be defined both on the configuration root level or on step level (in case of multiple step forms), but the field level debounce configuration will always take precedence.
By default the validation triggers only when the field's value changes (or when there is an event like submit or step changing that triggers validation), but you can define the dependencies
function to trigger the validation when the dependencies change.
If race conditions appear in case of customValidationAsync, those are automatically handled (if it gets called multiple times before resolving). You should supply the abortController
to the request handler (like fetch
), because in case of race conditions abort
will be called when necessary.
nestedArrayComponents
Property path:
configuration
> steps
> components
> nestedArrayComponents
Property type: FormComponentConfiguration[]
This is used for cases when dynamic nested arrays in which you can add or remove members.
Currently only one level of nesting is supported.
To be able to work more easily with dynamic nested arrays, the formStore
instance offers methods like addItemToNestedArrayField and removeItemFromNestedArrayField.
See the multi-step booking form example's "Personal details" step to learn how it works.
Wrapper component specific properties
Similarly to UI components, these do not hold values but instead of being rendered in the normal order, they wrap around other components (the components wrapped will be in the props.children
of this component).
Because of this, when creating them we must use props.children
.
It's recommended to avoid this type of component if possible and if we use a grid system in our FormWrapper
internal component, then it will be very rarely needed.
wrapping
Property path:
configuration
> steps
> components
> wrapping
Property type: "start" | "end"
In order to define which components this Wrapper component is wrapping, we will need to define the start
and end
of the wrapping.
Basically we need to add the component 2 times, once with wrapping = 'start'
and once with wrapping = 'end'
.
When defining it with wrapping = 'end'
we do not need to specify any configuration, only the type
and formComponentMappingKey
.
Events
We can define the important form-specific events that will get called inside methods like triggerGoToNextStep or triggerGoToPreviousStep.
Property path:
configuration
> events
optionaldefault: {}
Property type: Partial<FormEvents>
We have 6 events, for each of them we can define what should happen on triggering the event (usally async), what should happen if this throws an error and what should happen if it succeeds (these cannot be async):
- submit: Tied to the triggerSubmit method
- goToNextStep: Tied to the triggerGoToNextStep method
- goToPreviousStep: Tied to the triggerGoToPreviousStep method
- goToStep: Tied to the triggerGoToStep method
- save: Tied to the triggerSave method
- autoSave: Tied to the triggerAutoSave method
Form/step level validation
We can define validation on the step level (or form level in case of single-step configuration), not just on the field level. This can be useful when the potential validation error should not be tied to a single field, but to the step/form as a whole.
In addition to the fields being validated, this will also be called when step validation is triggered by events defined in validationOptions.
validate
Property path:
configuration
> steps
> validate
Property type: () => string
This will be called each time we try to submit the form or go to the next step, but we can also manually trigger it if needed.
If it returns empty string, the validation passes, otherwise the returned string will be the validation error.
Debounce can be set via the validationOptions
configuration property.
validateAsync
Property path:
configuration
> steps
> validateAsync
Property type: (abortController: AbortController) => Promise<string>
Same as validate
, but for async validation. If the sync validate
function returns a string, then the async validateAsync
function will not be called.
Sets the isValidatingStep
and stepValidationError
states accordingly in the formStore
.
If there are multiple triggers of this function before resolving, the race conditions are automatically handled. You should supply the abortController
to the request handler (like fetch
), because in case of race conditions abort
will be called when necessary.
Debounce can be set via the validationOptions.stepAsyncValidationDebounceDurationInMs configuration property.
validationOptions
Property path:
configuration
> steps
> validationOptions
Property type: ValidationOptions
This configuration object is used to fine tune the validation behaviour of the form. In most cases the defaults offer the best user experience.
Step validation in this configuration means refers to the step configuration's validate and validateAsync functions.
- shouldFocusFirstInvalidField: If
true
, the first invalid field will be focused when field validations are triggered when validating the step (or form in case of single step config). - validateFieldsOn: The events that trigger the validation of fields.
- reValidateFieldsOn: When the field's didTriggerRevalidationMode is
true
, then in addition to thevalidateFieldsOn
events, these events will also trigger the validation of field. - fieldValidationDebounceDurationInMs: The debounce duration for field validation.
- fieldAsyncValidationDebounceDurationInMs: The debounce duration for async field validation.
- validateStepOn: The events that trigger the validation of the step (or form in case of single step config).
- reValidateStepOn: When didTriggerRevalidationModeForStep is
true
, then in addition to thevalidateStepOn
events, these events will also trigger the validation of the step. - stepValidationDebounceDurationInMs: The debounce duration for step validation.
- stepAsyncValidationDebounceDurationInMs: The debounce duration for async step validation.
If we want to target specific fields with the (re)validateStepOn
or (re)validateFieldsOn
then we can use the excludeFieldNamesOnly
or includeFieldNamesOnly
properties (see type reference).
If this is defined both on the root and and step level, the step level configuration will override the root level configuration.
Initial data loading
In many cases our forms don't start empty, but it's prefilled with some information already entered at some point.
initialData
Property path:
configuration
> initialData
Property type: Record<string, any>
| Promise<Record<string, any>>
| () => Promise<Record<string, any>>
We can use this configuration to load remote initial data. It will handle the loading and error states correctly and the object this function returns (the keys should be the field names) will set the initial values of the fields.
While the async function we defined is being resolved, the Internal component InitialDataLoadingIndicator
will be rendered. If this async function throws an Error, then the Internal component InitialDataLoadingError
will be rendered.
We can also hard code the initial values of the fields here without any async operation (in this case the loading state will not be triggered).
Auto saving
Sometimes we want to save the form automatically that triggers on specific events that we can define in the autoSaveOptions
configuration.
autoSaveOptions
Property path:
configuration
> steps
> autoSaveOptions
optional
Property type: AutoSaveOptions
- enabled: If
true
, the auto save will be enabled. - autoSaveOn: The events that we define here will trigger the events.onAutoSave function. If we want to enable the autoSave only for specific fields, we can use the
excludeFieldNamesOnly
orincludeFieldNamesOnly
properties (see type reference). - autoSaveDebounceDurationInMs: The debounce duration for auto save.
- autoSaveIntervalInMs: The interval in milliseconds for auto save. If this is not
null
and autoSaveOn contains the valueinterval
, the auto save will be triggered in this interval without needing any user action. - ignoreValidation: If
true
, the auto save will trigger even if the validation of the form is not passing.
Context
For custom use cases that are not supported out of the box by react-flexyform
, this provides a flexible state slice that can be subscribed to and modified by any of the form components.
Property path:
configuration
> context
optionaldefault: {}
Learn how to access the form store in any configuration property that is a function.
If defined as a generic object, it will be the initial value of the context
.
It can also be defined in a dynamic way by providing it an object with a value
function that returns the parameters and an optional dependencies
function that will trigger the value
function when the dependencies change.
Multiple steps
A common use case is to split a form into multiple steps, for this we have to configure each step in the steps
array.
startAtStep
Property path:
configuration
> startAtStep
Property type: () => string
Learn how to access the form store in any configuration property that is a function.
When the form mounts (after the initialData is loaded), this function will be called to determine which step to start at.
steps
Property path:
configuration
> steps
Property type: StepConfiguration[]
For each step we can configure some previously mentioned properties:
Additionally, we have some other configurable properties that help us implement common use cases for multi-step forms.
name
Property path:
configuration
> steps
> name
required
Property type: string
This will be the identifier of the step if we want to refer to this step (similarly as for the components
> name
property) in any dynamic logic we implement.
shouldSkip
Property path:
configuration
> steps
> shouldSkip
Learn how to access the form store in any configuration property that is a function.
Property type: () => boolean
This is useful if we want to skip this step when a condition is met. This function is called each time we would navigate to this step to decide if we should skip to the next step instead.
shouldSkipWhenGoingToNextStep
Property path:
configuration
> steps
> shouldSkipWhenGoingToNextStep
Property type: () => boolean
Learn how to access the form store in any configuration property that is a function.
A more granular versions of the shouldSkip functionality which only gets called if we navigated to this step in the forward direction.
shouldSkipWhenGoingToPreviousStep
Property path:
configuration
> steps
> shouldSkipWhenGoingToPreviousStep
Property type: () => boolean
Learn how to access the form store in any configuration property that is a function.
A more granular versions of the shouldSkip functionality which only gets called if we navigated to this step in the backward direction.
nextStepDestination
Property path:
configuration
> steps
> nextStepDestination
Property type: () => string
Learn how to access the form store in any configuration property that is a function.
This is useful if we want to implement the jumping to a specific step when the triggerGoToNextStep is called from this step.
previousStepDestination
Property path:
configuration
> steps
> previousStepDestination
Property type: () => string
Learn how to access the form store in any configuration property that is a function.
This is useful if we want to implement the jumping to a specific step when the triggerGoToPreviousStep is called from this step.
onMount
Property path:
configuration
> steps
> onMount
Property type: () => any
Learn how to access the form store in any configuration property that is a function.
This function gets called when the step mounts.
onUnmount
Property path:
configuration
> steps
> onUnmount
Property type: () => any
Learn how to access the form store in any configuration property that is a function.
This function gets called when the step unmounts.
shouldSubmitOnEnter
Property path:
configuration
> steps
> shouldSubmitOnEnter
Property type: boolean
If this is set to true
the focus is inside a field in this step then by pressing Enter
the triggerSubmit function will be called.
shouldGoToNextStepOnEnter
Property path:
configuration
> steps
> shouldGoToNextStepOnEnter
Property type: boolean
If this is set to true
the focus is inside a field in this step then by pressing Enter
the triggerGoToNextStep function will be called.
shouldSaveOnEnter
Property path:
configuration
> steps
> shouldSaveOnEnter
Property type: boolean
If this is set to true
the focus is inside a field in this step then by pressing Enter
the triggerSave function will be called.
Form store instance properties
We can have access to the formStore
instance anywhere:
- in the configuration via the
getFormStoreState
function - in the form components via the useParentFormStore hook
- anywhere in our React app via the useCreateFormStore and
useFormStore
hooks
Let's see what properties and methods we can access from the formStore
instance.
configuration
Property path:
formStore
> configuration
Property type: CreateStoreMultiStepConfiguration
This is the configuration that we used to create the store with. It can be useful to have access to this if we want to do something based on the configuration.
It's important to note that even though the original configuration was for a single step form without defining the steps
, this will always be using steps
(it will have one step in case of a single step form).
Also, even if the name
was not provided for some of the components, the auto-generated name
properties will be available.
context
Property path:
formStore
> context
Property type: Record<string, any>
For custom use cases that are not supported out of the box by react-flexyform
, this provides a flexible state slice that can be subscribed to and modified.
setContext
Property path:
formStore
> setContext
Property type: (context: Record<string, any>) => void
The setter of the context property. Does not completely replace the current value, it updates only the properties defined in the context
argument.
initialData
Property path:
formStore
> initialData
Property type: { [fieldName: string]: any } | null
The initial values of the fields when the form was mounted.
In the case in which we supplied an async function to the initialData configuration, then we will be able to access its resolved value through this property.
The value is null
only if the initial data is loading.
isLoadingInitialData
Property path: formStore
> isLoadingInitialData
Property type: boolean
Only relevant in case there is an async function supplied to the initialData configuration.
If this is true
the form will render the InitialDataLoadingIndicator internal form component.
This value is modified by the isLoading
prop on the <Form> component or by the triggerInitialDataLoading method of the formInstance
.
initialDataLoadingError
Property path: formStore
> initialDataLoadingError
Property type: Error | null
Only relevant in case there is an async function supplied to the initialData configuration.
If there is an error, the form will render the InitialDataLoadingError internal form component.
This value is modified by the loadingError
prop on the <Form> component or by the triggerInitialDataLoading method of the formInstance
.
triggerInitialDataLoading
Property path: formStore
> triggerInitialDataLoading
Property type: () => Promise<void>
This is useful if we want to implement retry functionality in the InitialDataLoadingError internal form component.
Changes isLoadingInitialData, initialDataLoadingError` and initialData.
currentStepName
Property path: formStore
> currentStepName
Property type: string
The name identifier of the current step.
stepHistory
Property path: formStore
> stepHistory
Property type: string[]
Contains the name identifier of the steps that were visited.
lastDirection
Property path: formStore
> lastDirection
Property type: 'idle' | 'previous' | 'next'
Useful if we want to implement animations in multi step forms when switching between steps.
The idle
value is only set when there were no other steps visited yet, just the initial step.
triggerStepValidation
Property path: formStore
> triggerStepValidation
Property type: () => Promise<boolean>
Runs the validate and validateAsync logic defined on the step. If the sync validation fails, the async one does not get run.
It does not trigger field validations. For that we can use the triggerFieldValidationsForStep method.
Sets isValidatingStep, stepValidationError and didTriggerRevalidationModeForStep on the formStore
instance.
isValidatingStep
Property path: formStore
> isValidatingStep
Property type: boolean
Set by the triggerStepValidation method.
stepValidationError
Property path: formStore
> stepValidationError
Property type: string
Set by the triggerStepValidation method.
didTriggerRevalidationModeForStep
Property path: formStore
> didTriggerRevalidationModeForStep
Property type: boolean
Starts as false
and gets set to true
if the step validation fails by the events that trigger step validation defined in validationOptions.validateStepOn.
If this is true
then the validationOptions.reValidateStepOn events will also trigger the step validation.
eventHistory
Property path: formStore
> eventHistory
Property type: { stepName: string type: 'goToNextStep' | 'goToPreviousStep' | 'goToStep' | 'save' | 'submit' }
Contains the history of the events triggered and the step they got triggered on.
resetFormState
Property path: formStore
> resetFormState
Property type: () => void
Resets the formStore
instance's state to its initial values.
getIsStepDirty
Property path: formStore
> getIsStepDirty
Property type: () => boolean
Returns true
if there were any changes compared to the initial values in any of the fields from the step, otherwise returns false
.
getIsStepTouched
Property path: formStore
> getIsStepTouched
Property type: () => boolean
Returns true
if there was user input (or onBlur
event trigger) in any of the fields from the step (even if the values are equal to the initial values), otherwise returns false
.
getIsValidatingAnything
Property path: formStore
> getIsValidatingAnything
Property type: () => boolean
Returns true
if the any of the fields are validating or the step is validating, otherwise returns false
.
getFirstStepNameWithRequiredFieldsNotCompleted
Property path: formStore
>
getFirstStepNameWithRequiredFieldsNotCompleted
Property type: () => string
Returns the step name of the first step that does not have all required fields completed. Can be useful for determining at which step to start at after initial data fetching.
getCurrentStep
Property path: formStore
> getCurrentStep
Property type: () => Step
getStepByName
Property path: formStore
> getStepByName
Property type: (stepName: string) => Step
getStepByIndex
Property path: formStore
> getStepByIndex
Property type: (stepIndex: number) => Step
getNextStepName
Property path: formStore
> getNextStepName
Property type: (stepName?: string) => string
getPreviousStepName
Property path: formStore
> getPreviousStepName
Property type: (stepName?: string) => string
getStepIndexByName
Property path: formStore
> getStepIndexByName
Property type: (stepName: string) => number
getStepNameByIndex
Property path: formStore
> getStepNameByIndex
Property type: (stepIndex: number) => string
getCurrentStepIndex
Property path: formStore
> getCurrentStepIndex
Property type: () => number
getFirstStep
Property path: formStore
> getCurrentStepIndex
Property type: () => Step
getLastStep
Property path: formStore
> getCurrentStepIndex
Property type: () => Step
getIsLastStep
Property path: formStore
> getIsLastStep
Property type: () => boolean
Returns true
if the current step is the last step.
getIsFirstStep
Property path: formStore
> getIsFirstStep
Property type: () => boolean
Returns true
if the current step is the first step.
getFieldNamesByStepIndex
Property path: formStore
> getFieldNamesByStepIndex
Property type: (stepIndex: number) => string[]
getFieldNamesByStepName
Property path: formStore
> getFieldNamesByStepName
Property type: (stepName: string) => string[]
fields
Property path: formStore
> fields
The state of all the form fields are stored here, but there are better ways of interracting with them than accessing them directly from this object.
- id: You should assign this to the field's main focusable element for the focusing features to work
- name: The name of the field (auto-generated in case if not defined in the configuration)
- stepName: The name of the step the field is in
- value: The current value of the field
- initialValue: The initial value of the field when the form was mounted
- stepInitialValue: The initial value of the field in the step when the step was mounted
- previousValue: The value of the field before the last change
- isTouched:
true
if there was any user input (or onBlur event triggered), even if the value remains equal to the initial value - gets reset when step change occurs - isDirty:
true
if the field's value is different from the initial value - gets reset when step change occurs - isValidating:
true
if the field is validating - validationError: The validation error of the field
- didPassAsyncValidation:
true
if the validationRules.customValidationAsync ran and passed - didTriggerRevalidationMode: Starts as
false
and gets set totrue
if the field's validation failed. After that in addition to the validationOptions.validateFieldsOn events, the validationOptions.reValidateFieldsOn events will also trigger the field's validation. If the field gets validated and passes validation, it will get set tofalse
. - lastValidatedValue: The value of the field when the last validation was triggered
setFieldValue
Property path: formStore
> setFieldValue
Property type: (fieldName: string, value: any, options\?: \{ shouldSetIsDirty?: boolean; shouldSetIsTouched?: boolean; shouldTriggerValidation?: boolean; \}) => void
A method to imperatively change the value
property of a field (also sets previousValue
). By default it will not trigger validation or set the isDirty
and isTouched
states, but this can be modified in the options
parameter.
triggerFieldBlur
Property path: formStore
> triggerFieldBlur
Property type: (fieldName: string) => void
- sets the
isTouched
property - triggers field validation if it's set in the validationOptions
- triggers auto saving if it's set in the autoSaveOptions
This should be used in the onBlur
event handler of the field.
triggerFieldChange
Property path: formStore
> triggerFieldChange
Property type: (fieldName: string, value: any, options?: { shouldDisableHtmlEventHandling?: boolean }) => void
- sets the
isTouched
,isDirty
,value
(alsopreviousValue
) properties - triggers field validation if it's set in the validationOptions
- triggers auto saving if it's set in the autoSaveOptions
By default if the value
supplied to this method extends the html event types of { target: any }
or { checked: boolean }
, then the field's value
will be set to event.target.value
or event.target.checked
respectively. If we want to disable this behaviour, we can set the shouldDisableHtmlEventHandling
option to true
.
This should be used in the onChange
event handler of the field.
addItemToNestedArrayField
Property path: formStore
> addItemToNestedArrayField
Property type: (fieldName: string, defaultValues?: { [nestedFieldName: string]: any }) => void
Appends a new item to a nested array field. Follows the same logic as the triggerFieldChange method.
removeItemFromNestedArrayField
Property path: formStore
> removeItemFromNestedArrayField
Property type: (fieldName: string, indexToRemove: number) => void
Removes an item from the nested array field. Follows the same logic as the triggerFieldChange method.
triggerFieldFocus
Property path: formStore
> triggerFieldFocus
Property type: (fieldName: string) => boolean
Focuses the field and returns true
if the field was focused successfully.
resetField
Property path: formStore
> resetField
Property type: (fieldName: string, options?: { shouldKeepValue?: boolean; shouldKeepIsDirty?: boolean; shouldKeepIsTouched?: boolean; shouldKeepValidationError?: boolean }) => void
Resets the states of field.
We can control if we do not want to reset any of the states with the options
argument.
resetFieldsForStep
Property path: formStore
> resetFieldsForStep
Property type: (stepName?: string, options?: { shouldKeepValue?: boolean; shouldKeepIsDirty?: boolean; shouldKeepIsTouched?: boolean; shouldKeepValidationError?: boolean }) => void
Calls resetField for all the fields in the step that matches the stepName
argument, passing the options
to them. If stepName
is ommited, the step will default to the current step.
triggerFieldValidation
Property path: formStore
> triggerFieldValidation
Property type: (fieldName: string, options?: { shouldFocusInvalidField?: boolean }) => Promise<boolean>
Runs the validationRules we defined field's configuration.
Returns true
if the validation is successfully passed, returns false
if there is any validation error.
If the shouldFocusInvalidField
option is set to true
then the field will be focused if there is a validation error.
If the sync validation fails, the async validation does not get run.
Sets isValidating (only if validateAsync
is being run) and validationError properties in the field's state.
triggerMultipleFieldsValidations
Property path: formStore
> triggerMultipleFieldsValidations
Property type: (fieldNames: string[], options?: { shouldFocusInvalidField?: boolean }) => Promise<boolean>
Calls triggerFieldValidation for multiple fields at the same time.
triggerFieldValidationsForStep
Property path: formStore
> triggerFieldValidationsForStep
Property type: (options?: { shouldFocusInvalidField?: boolean }) => Promise<boolean>
Calls triggerFieldValidation for all the fields in the current step.
resetValidationErrorForFields
Property path: formStore
> resetValidationErrorForFields
Property type: (fieldNames: string[]) => void
Sets the validationError to null
in the fields' states that match the fieldNames
argument.
resetFieldValidationErrorsForStep
Property path: formStore
> resetFieldValidationErrorsForStep
Property type: () => void
Sets the validationError
to null
in all the fields' states in the current step.
getFieldValue
Property path: formStore
> getFieldValue
Property type: (fieldName: string) => any
Returns undefined
if no field is registered with the fieldName
argument, otherwise returns the value
of the field.
getStepFieldValues
Property path: formStore
> getStepFieldValues
Property type: (stepName?: string) => { [fieldName: string]: any } | undefined
Returns the field values from a specific step that are showing.
If stepName
is not defined it will default to the current step name.
Returns undefined
if there is no step that matches the stepName
argument.
getAllFieldValues
Property path: formStore
> getAllFieldValues
Property type: () => { [fieldName: string]: any }
Returns all field values from all steps that are showing.
getField
Property path: formStore
> getField
Property type: (fieldName: string) => Field | undefined
Returns the whole state of the field if it exists, otherwise returns undefined
.
getNestedArrayItemField
Property path: formStore
> getNestedArrayItemField
Property type: (params: { parentFieldName: string; fieldName: string; nestedItemIndex: number }) => Field | undefined
Returns the whole state of a specific nested array item field if it exists, otherwise returns undefined
.
getIsFormComponentShowing
Property path: formStore
> getIsFormComponentShowing
Property type: (formComponentName: string, options?: { shouldConsiderScreenSize?: boolean }) => boolean
It returns true
or false
based on the form component configuration's shouldShowOnlyIf, shouldShowOnlyIfMediaQueryMatches and shouldShowOnlyOnScreenSize properties.
We can opt out of the shouldShowOnlyIfMediaQueryMatches and shouldShowOnlyOnScreenSize logic by setting the shouldConsiderScreenSize
option to false
.
getStepFields
Property path: formStore
> getStepFields
Property type: (stepName?: string) => { [fieldName: string], Field }
Gets the state of the fields in a specific step that are showing.
If the stepName
is not defined, it will default to the current step.
getAllFields
Property path: formStore
> getAllFields
Property type: () => { [fieldName: string], Field }
Gets the state of the fields from all steps that are showing.
getDirtyFields
Property path: formStore
> getDirtyFields
Property type: () => FormFieldConfiguration[]
Returns an array with all the fields whose isDirty
property is true
.
getTouchedFields
Property path: formStore
> getTouchedFields
Property type: () => FormFieldConfiguration[]
Returns an array with all the fields whose isTouched
property is true
.
getInvalidFields
Property path: formStore
> getInvalidFields
Property type: () => FormFieldConfiguration[]
Returns an array with all the fields whose validationError
property is not null
.
getRequiredFields
Property path: formStore
> getRequiredFields
Property type: () => FormFieldConfiguration[]
Returns an array with all the fields whose validationRules.required property is defined.
getRequiredFieldsForStep
Property path: formStore
> getRequiredFieldsForStep
Property type: (stepName?: string) => FormFieldConfiguration[]
Returns an array with all the fields whose validationRules.required property is defined for the given stepName
(defaults to the currentStepName
).
getIsValidatingFields
Property path: formStore
> getIsValidatingFields
Property type: () => boolean
Returns true
if any field is validating.
getIsAnyFieldInvalid
Property path: formStore
> getIsAnyFieldInvalid
Property type: () => boolean
Returns true
if any field's validationError
is not null.
getIsAnyFieldFocused
Property path: formStore
> getIsAnyFieldFocused
Property type: () => boolean
Returns true
if any of the fields are focused. For this to work, you must assign the field's id property to the field's focusable input element.
getFieldValidationError
Property path: formStore
> getFieldValidationError
Property type: (fieldName: string) => string[] | null
Returns the validationError
of the field.
componentParams
Property path: formStore
> componentParams
All the componentParams that are set in the configuration are stored here.
In case of async component params, the isLoading
and loadingError
properties will be set accordingly.
getComponentParams
Property path: formStore
> getComponentParams
Property type: (componentName: string) => Record<string, any> | undefined
Gets the value
property of the component params for the componentName
.
getAreComponentParamsLoading
Property path: formStore
> getAreComponentParamsLoading
Property type: `(componentName: string) => true
Gets the isLoading
property of the component params for the componentName
.
getComponentParamsLoadingError
Property path: formStore
> getComponentParamsLoadingError
Property type: (componentName: string) => Error | null
Gets the loadingError
property of the component params for the componentName
.
setComponentParams
Property path: formStore
> setComponentParams
Property type: (componentName: string, componentParams: Record<string, any>) => void
Sets the value
property of the component params for the componentName
. Does not completely replace the current value, it updates only the properties defined in the componentParams
argument.
triggerDynamicComponentParams
Property path: formStore
> triggerDynamicComponentParams
Property type: (componentName: string) => Promise<void>
Triggers the dynamic component params function for the componentName
. Sets isLoading
and loadingError
for the componentParams accordingly.
triggerAllDynamicComponentParamsForStep
Property path: formStore
>
triggerAllDynamicComponentParamsForStep
Property type: () => Promise<void>
Runs triggerDynamicComponentParams for all the components in the current step.
getAreAnyComponentParamsLoading
Property path: formStore
> getAreAnyComponentParamsLoading
Property type: () => boolean
Returns true
if any of the component params are loading.
getAreAnyComponentParamsLoadingErrors
Property path: formStore
>
getAreAnyComponentParamsLoadingErrors
Property type: () => boolean
Returns true
if any of the component params have loading error.
getComponentsWithComponentParamsLoadingError
Property path: formStore
>
getComponentsWithComponentParamsLoadingError
Property type: () => FormComponentConfiguration[]
Returns the FormComponentConfiguration of all the components that have component params loading error.
getComponentConfiguration
Property path: formStore
> getComponentConfiguration
Property type: (componentName: string) => FormComponentConfiguration | undefined
Returns the FormComponentConfiguration of the component.
triggerGoToNextStep
Property path: formStore
> triggerGoToStep
Property type: (params?: { onGoToNextStep?: () => Promise<any> | any; onGoToNextStepError?: () => any; }) => Promise<void>
Use this to go to the next step.
The following sequence happens when calling this function:
- Triggers all the field validations and step validation if configured in the validationOptions. If there are validation errors, then it will not navigate to the step.
If there are no validation errors, then:
- Calls the events.onGoToNextStep function (can be overriden by defining
onGoToNextStep
in the arguments) and sets isChangingStep and stepChangeError if the function is asynchronous - If
onGoToNextStep
throws an error then if provided the events.onGoToNextStepError (can be overriden by definingonGoToNextStepError
in the arguments) function gets called - If
onGoToNextStep
was successful then if provided the events.onGoToNextStepSuccess (can be overriden by definingonGoToNextStepSuccess
in the arguments) function gets called - Sets the currentStepName to the previous step considering the shouldSkipWhenGoingToNextStep, shouldSkipIf and nextStepDestination configurations.
- Resets the fields' isDirty and isTouched properties
triggerGoToPreviousStep
Property path: formStore
> triggerGoToPreviousStep
Property type: (params?: { onGoToPreviousStep?: () => Promise<any> | any, onGoToPreviousStepError?: () => any, onGoToPreviousStepSuccess?: () => any, shouldKeepFieldValues?: boolean; }) => Promise<void>
Use this to go to the previous step.
The following sequence happens when calling this function:
- Triggers all the field validations and step validation if configured in the validationOptions. If there are validation errors, then it will not navigate to the step.
If there are no validation errors, then:
- Calls the events.onGoToPreviousStep function (can be overriden by defining
onGoToPreviousStep
in the arguments) and sets isChangingStep and stepChangeError if the function is asynchronous - If
onGoToPreviousStep
throws an error then if provided the events.onGoToPreviousStepError (can be overriden by definingonGoToPreviousStepError
in the arguments) function gets called - If
onGoToPreviousStep
was successful then if provided the events.onGoToPreviousStepSuccess (can be overriden by definingonGoToPreviousStepSuccess
in the arguments) function gets called - Sets the currentStepName to the previous step considering the shouldSkipWhenGoingToPreviousStep, shouldSkipIf and previousStepDestination configurations.
- By default it resets the values of the step you navigated back from unless the
shouldKeepFieldValues
is set totrue
- Resets the fields' isDirty and isTouched properties
triggerGoToStep
Property path: formStore
> triggerGoToStep
Property type: (params: { stepName: string, onGoToStep?: () => Promise<any> | any, onGoToStepError?: () => any, onGoToStepSuccess?: () => any }) => Promise<void>
Use this to navigate between steps.
The following sequence happens when calling this function:
- Triggers all the field validations and step validation if configured in the validationOptions
If there are no validation errors, then:
- Calls the events.onGoToStep function (can be overriden by defining
onGoToStep
in the arguments) and sets isChangingStep and stepChangeError if the function is asynchronous - If
onGoToStep
throws an error then if provided the events.onGoToStepError (can be overriden by definingonGoToStepError
in the arguments) function gets called - If
onGoToStep
was successful then if provided the events.onGoToStepSuccess (can be overriden by definingonGoToStepSuccess
in the arguments) function gets called - Sets the currentStepName to the
stepName
from the params considering the shouldSkipWhenGoingToNextStep, shouldSkipIf and nextStepDestination configurations (or the corrsponding previous step alternatives if thestepName
is before the current step). - Resets the fields' isDirty and isTouched properties
isChangingStep
Property path: formStore
> isChangingStep
Property type: boolean
Set by the triggerGoToNextStep, triggerGoToPreviousStep and triggerGoToStep method.
isGoingToNextStep
Property path: formStore
> isGoingToNextStep
Property type: boolean
Set by the triggerGoToNextStep method.
isGoingToPreviousStep
Property path: formStore
> isGoingToPreviousStep
Property type: boolean
Set by the triggerGoToPreviousStep method.
stepChangeError
Property path: formStore
> stepChangeError
Property type: Error | null
Set by the triggerGoToNextStep, triggerGoToPreviousStep and triggerGoToStep methods.
triggerSave
Property path: formStore
> triggerSave
Property type: (params?: { onSave?: (abortController: AbortController) => Promise<any> | any, onSaveError?: () => any, onSaveSuccess?: () => any }) => Promise<void>
Useful for implementing saving without submitting.
The following sequence happens when calling this function:
- Triggers all the field validations and step validation if configured in the validationOptions
If there are no validation errors, then:
- Calls the events.onSave function (can be overriden by defining
onSave
in the arguments) and sets isSaving and saveError if the function is asynchronous - On every field in the current step sets the isDirty and isTouched properties to
true
and the stepInitialValue the current value of the field (this is useful if we want to disable the 'Save' button in case the form was not modified) - If
onSave
throws an error then if provided the events.onSaveError (can be overriden by definingonSaveError
in the arguments) function gets called - If
onSave
was successful then if provided the events.onSaveSuccess (can be overriden by definingonSaveSuccess
in the arguments) function gets called
If there are multiple triggers of this function before resolving, the race conditions are automatically handled. You should supply the abortController
to the request handler (like fetch
), because in case of race conditions abort
will be called when necessary.
isSaving
Property path: formStore
> isSaving
Property type: boolean
Set by the triggerSave method.
saveError
Property path: formStore
> saveError
Property type: Error | null
Set by the triggerSave method.
triggerAutoSave
Property path: formStore
> triggerAuto
Property type: (params: { onAutoSave?: (abortController: AbortController) => Promise<any> | any, onAutoSaveError?: () => any, onAutoSaveSuccess?: () => any }) => Promise<void>
- Calls the events.onAutoSave or autoSaveOptions.onAutoSave function (can be overriden by defining
onAutoSave
in the arguments) and sets isAutoSaving and autoSaveError if the function is asynchronous - If
onAutoSave
throws an error then if provided the events.onAutoSaveError or autoSaveOptions.onAutoSaveError (can be overriden by definingonAutoSaveError
in the arguments) function gets called - If
onAutoSave
was successful then if provided the events.onAutoSaveSuccess or autoSaveOptions.onAutoSaveSuccess (can be overriden by definingonAutoSaveSuccess
in the arguments) function gets called
Sets [isAutoSaving] and [autoSaveError] if the function is asynchronous.
If there are multiple triggers of this function before resolving, the race conditions are automatically handled. You should supply the abortController
to the request handler (like fetch
), because in case of race conditions abort
will be called when necessary.
Debounce can be set via the autoSaveOptions.autoSaveDebounceDurationInMs configuration property.
isAutoSaving
Property path: formStore
> isSaving
Property type: boolean
Set by the triggerAutoSave method.
autoSaveError
Property path: formStore
> saveError
Property type: Error | null
Set by the triggerAutoSave method.
triggerSubmit
Property path: formStore
> triggerSubmit
Property type: (params?: { onSubmit?: () => Promise<any> | any, onSubmitError?: () => any, onSubmitSuccess?: () => any }) => Promise<void>
Useful for implementing form submission.
The following sequence happens when calling this function:
- Triggers all the field validations and step validation (cannot be opted out from)
If there are no validation errors, then:
- Calls the events.onSubmit function (can be overriden by defining
onSubmit
in the arguments) and sets isSubmitting and submitError if the function is asynchronous - If
onSubmit
throws an error then if provided the events.onSubmitError (can be overriden by definingonSubmitError
in the arguments) function gets called - If
onSubmit
was successful then if provided the events.onSubmitSuccess (can be overriden by definingonSubmitSuccess
in the arguments) function gets called
isSubmitting
Property path: formStore
> isSubmitting
Property type: boolean
Set by the triggerSubmit method.
submitError
Property path: formStore
> submitError
Property type: Error | null
Set by the triggerSubmit method.
didSubmitSuccessfully
Property path: formStore
> didSubmitSuccessfully
Property type: boolean
Set by the triggerSubmit method.
Hooks
We can access and manage state in a convenient way in dynamic form components through hooks.
useCreateFormStore
Hook type:This hook is used to create a new formStore
instance.
We can use the formStoreKey
to refer to this form store anywhere in our app by supplying it to the useFormStore hook.
If the resetStoreOnMount
option is set to false
, the form store will not reset its state when a <Form> component is mounted again with the formStore
instance with the given formStoreKey
.
useCreateFormStoreAsync
Hook type:This hook can be useful if the form configuration depends on some async data.
In addition to the formStore
instance, it also returns isLoading
, loadingError
which can be supplied to the <Form> component to handle the loading and error states in a consistent manner by rendering the InitialDataLoadingIndicator and InitialDataLoadingError internal components when needed.
useFormStore
Hook type: (formStoreKey: string) => FormStore | undefined
This hook is used to access the formStore
instance anywhere in our app that was created with the useCreateFormStore hook with the given formStoreKey
.
useFormComponentName
Hook type: () => string
This hook returns the name
(from the configuration) of the dynamic form component it's used in.
Many methods require us to pass the name
of the form component in order to work.
useNestedArrayItemInfo
Hook type: () => { isNestedArrayItem: boolean; parentFieldName: string; nestedItemIndex: number; nestedItemComponentName: string; }
In case we want to implement nested array component specific logic, we can use this hook to get the necessary information.
If the component is not used as a nested array component, then the isNestedArrayItem
will be false
.
For example if a field is used in a nested array, its name will contain the parent field's name, the index and the actual field's.
In this case we will have friends[0].firstName, friends[0].lastName as the field name of the nested array component.
Additionally we will a component with the name friends[0].autoGeneratedNameForRemoveNestedArrayItemButton (as the name is not defined)
This hook will yield:
{ isNestedArrayItem: true, parentFieldName: 'friends', nestedItemIndex: theCurrentIndex, nestedItemComponentName: 'firstName' }
{ isNestedArrayItem: true, parentFieldName: 'friends', nestedItemIndex: theCurrentIndex, nestedItemComponentName: 'lastName' }
{ isNestedArrayItem: true, parentFieldName: 'friends', nestedItemIndex: theCurrentIndex, nestedItemComponentName: 'autoGeneratedNameForRemoveNestedArrayItemButton' }
useParentFormStore
Hook name:useParentFormStore
Hook type: <FormStoreSlice>((formStore: FormStore) => FormStoreSlice) => FormStoreSlice
This is the primary hook for accessing the form store state in the form components.
This hook together with the useFormComponentName hook an be used to implement any functionality.
Sometimes the useNestedArrayItemInfo hook is also needed when working with nested array components.
For performance reasons, you should always select only state slice you need.
If you want to return an object from the selector function, you should use the
isDeepEqual
function to ensure no unnecessary re-renders occur.
useField
Hook type:This hook should be used inside of field component as it returns a convenient interface to work with.
All the methods are already bound to the field name, so we don't need to pass the field name as an argument.
useFormComponentParams
Hook type: (formComponentName?: string) => string
This hook returns the componentParams
(from the configuration) of the form component it's used in.
useSetFormComponentParams
Hook type: () => (componentParams: Record<string, any>) => void
This hook returns a function which can be used to modify the componentParams
of the form component it's used in.
React components
FormComponentMappingsProvider
Component type:In order for react-flexyform
to work, we need to wrap our application with this component.
We need to bind the components here that we want to use in the form configuration.
The FormComponentMappingsProvider
component accepts 4 different categories of form component mappings that we need to supply:
- Field components: These are the typical form components that capture user input and go through validation.
- UI components: These components can fulfill many essential roles in the form such as buttons to trigger events like submission, a tracking indicator of the form's steps, confirmation modals, white spaces, etc.
- Wrapper components: These components can used to wrap other components to create the responsive layouts for our forms. If we use a grid system for our FormWrapper, we can define the layout of our form fields efficiently.
- Internal components: These components are not defined in any single form's configuration, but they are part of every form by default. There are 3 internal components that we can define:
- <FormWrapper>: This component is the default wrapper for every form. It is recommended to set this up globally to use the same layout strategy for all of our forms. Ideally we want to use a grid system (for example Mantine Grid) to define the layout of our form fields. We must render
props.children
inside this component. - <ComponentWrapper>: This component will wrap every field component in the form. It is recommended to define them in a way that these accept layout related parameters through componentParams (like margins, grid positioning, etc.). We must render
props.children
inside this component. - <InitialDataLoadingIndicator>: This component will be rendered only if an async data loading operation defined in the form's initialData configuration to load the initial values of the fields is still loading.
- <InitialDataLoadingError>: This component will be rendered only if the async data loading operation defined in the form's initialData configuration to load the initial values of the fields threw an error.
- <FormWrapper>: This component is the default wrapper for every form. It is recommended to set this up globally to use the same layout strategy for all of our forms. Ideally we want to use a grid system (for example Mantine Grid) to define the layout of our form fields. We must render
Form
Component type:This is the main component that renders the form components. If isInitialDataLoading
is defined, it will sync the formStore
instance's isLoadingInitialData property with it. Same goes for the initialDataLoadingError
and the formStore
instance's initialDataLoadingError property.
NestedArrayComponents
Component type:This component is used to build generic dynamic components that accept nestedArrayComponents in the configuration.
You need to define a render
function that will render the nested array components defined configuration the same way the <Form> component renders the components, but this way you can dynamically style the wrapper of the nested array components.
Type reference
CreateStoreConfiguration
CreateStoreSingleStepConfiguration | CreateStoreMultiStepConfiguration
CreateStoreSingleStepConfiguration
Other types used in this type: FormComponentConfiguration, ValidationOptions, AutoSaveOptions, FormEvents
CreateStoreMultiStepConfiguration
Other types used in this type: StepConfiguration, FormEvents
StepConfiguration
Other types used in this type: FormComponentConfiguration, ValidationOptions, AutoSaveOptions
FormComponentConfiguration
FormFieldComponentConfiguration | FormUiComponentConfiguration | FormWrapperComponentConfiguration
FormFieldComponentConfiguration
Other types used in this type: FieldValidationRules
FormUiComponentConfiguration
FormWrapperComponentConfiguration
FieldValidationRules
FormEvents
ValidationOptions
AutoSaveOptions
FormStore
Other types used in this type: Field