Solid Forms provides several form control objects useful for making working with forms easier. Demos and examples below.
# solidjs
yarn add solid-forms
# or
npm install solid-forms
Note: Solid Forms is brand new and should be considered “beta” until the version release hits 1.0.0. This means breaking changes can come in minor releases. We are following semantic versioning.
The basic building block of Solid Forms are FormControls (see IFormControl API). A FormControl is intended to model a single input element of a form. For example, an <input /> element or a radio button group. You can use a FormControl to save the value of the input, to handle validation and track errors, to track whether the input has been touched, changed, submitted, etc. Importantly, the FormControl itself is just a Solidjs Store object so all of it’s properties are observable and you can easily respond to changes (e.g. with createEffect() or just using the control values inside of a component directly). All form controls in solid-forms are immutable unless you use the provided methods to update their state.
import {Show,mergeProps,typeComponent}from"solid-js";import{createFormControl}from"solid-forms";exportconstTextInput: Component<{control?: IFormControl<string>;name?: string;type?: string;}>=(props)=>{// here we provide a default form control in case the user doesn't supply oneprops=mergeProps({control: createFormControl(""),type: "text"},props);return(<divclassList={{"is-invalid": !!props.control.errors,"is-touched": props.control.isTouched,"is-required": props.control.isRequired,}}><inputname={props.name}type={props.type}value={props.control.value}oninput={(e)=>{props.control.setValue(e.currentTarget.value);}}onblur={()=>props.control.markTouched(true)}required={props.control.isRequired}/><Showwhen={props.control.isTouched&&props.control.errors?.isMissing}><small>Answer required.</small></Show></div>);};
But the real power of FormControls comes from their composability with other controls such as FormGroups (see IFormGroup API) and FormArrays (see IFormArray API).
import {Show,mergeProps,createEffect,typeComponent}from"solid-js";import{createFormGroup,createFormControl}from"solid-forms";// here we import the TextInput component we defined aboveimport{TextInput}from"./TextInput";exportconstExampleForm: Component<{}>=()=>{constgroup=createFormGroup({name: createFormControl(""),email: createFormControl("",{required: true,validators: (value: string)=>value.length===0 ? {isMissing: true} : null,}),});// This will automatically re-run whenever `group.isDisabled`, `group.isValid` or `group.value` changecreateEffect(()=>{if(group.isDisabled||!group.isValid)return;console.log("Current group value",group.value);});constonSubmit=async()=>{if(group.isSubmitted||!group.isValid)return;group.markSubmitted(true);// do stuff...// const { name, email } = group.value;};return(<formonSubmit={onSubmit}><labelfor="name">Your name</label><TextInputname="name"control={group.controls.name}/><labelfor="email">Your email address</label><TextInputname="email"type="email"control={group.controls.email}/><button>Submit</button></form>);};
Lets begin by looking at how to use FormControls to model individual form fields, then we’ll learn how to use FormGroups and FormArrays to model forms and form fieldsets (i.e. partial forms).
To model individual form fields, we’ll use FormControls created via createFormControl(). We can create a FormControl with an initial value of "" like so:
If we want to set the value on our FormControl we can do the following (note, all control objects in solid-forms are immutable so you need to use the provided methods–e.g. setValue()–to update their state):
control.setValue("Hi");control.value;// "Hi"
We can also mark our FormControl as touched (or required, disabled, readonly, submitted, pending, or dirty).
control.markTouched(true);control.touched;// truecontrol.markTouched(false);// you get the idea
We can manually add errors to our FormControl
control.errors;// nullcontrol.isValid;// truecontrol.setErrors({required: true});control.errors;// { required: true }control.isValid;// falsecontrol.patchErrors({tooLong: "must be less than 5 characters"});control.errors;// { required: true, tooLong: "must be less than 5 characters" }
We can add a validation function (or functions) which will be run after every value change
Under-the-hood, FormControls are just Solidjs stores so we also have the ability to observe any changes to those properties with Solidjs.
createEffect(()=>{console.log("Value change: ",control.value);});// here we manually run validation inside of a render effectcreateRenderEffect(()=>{if(control.value.toLowerCase()!==control.value){control.setErrors({mustBeLowercase: true});}else{control.setErrors(null);}});<divclassList={{"is-invalid": !!control.errors}}><p>The control's current value is: {JSON.stringify(control.value)}</p></div>;
Lets revist our TextInput example from above (Stackblitz demo),
import {Show,For,mergeProps,typeComponent}from"solid-js";import{createFormControl}from"solid-forms";exportconstTextInput: Component<{control?: IFormControl<string>;name?: string;type?: string;}>=(props)=>{// here we provide a default form control in case the user doesn't supply oneprops=mergeProps({control: createFormControl(""),type: "text"},props);return(<divclassList={{"is-invalid": !!props.control.errors,"is-touched": props.control.isTouched,"is-required": props.control.isRequired,"is-disabled": props.control.isDisabled,}}><inputname={props.name}type={props.type}value={props.control.value}oninput={(e)=>{props.control.setValue(e.currentTarget.value);}}onblur={()=>props.control.markTouched(true)}required={props.control.isRequired}disabled={props.control.isDisabled}/><Showwhen={props.control.isTouched&&!props.control.isValid}><Foreach={Object.values(props.control.errors)}>{(errorMsg: string)=><small>{errorMsg}</small>}</For></Show></div>);};
Breaking this example down: we’d like the ability for a parent component to pass in a FormControl for the TextInput to use, but we’d also like the TextInput to be usable on its own if the parent doesn’t supply a control value. We can accomplish that just like any other Solidjs component using mergeProps from the Solidjs core library to provide a default control prop value if the user doesn’t supply one.
Since FormControl (and FormGroups and FormArrays) are just Solidjs stores under-the-hood, we can easily use the core classList prop to add css classes to our TextInput if it is invalid, touched, required, or disabled.
We set the underlying <input /> element to be equal to the FormControl’s value (value={props.control.value}), we react to input value changes and update the FormControl’s value (props.control.setValue(e.currentTarget.value)), we mark the control as touched on blur events, and we setup the input to be required and disabled if the FormControl is required or disabled.
Finally, we decide to show errors associated with this FormControl if the control isn’t valid AND if the control has been touched. When this happens, we show all the error messages associated with the control.
Validating form data and working with errors is a core part of handling user input. The are two primary ways of validating data in Solid Forms. The simple but more limited approach is to use validator functions. The more flexible and powerful approach is to just use Solidjs built-ins like createEffect() to observe control changes and then setErrors() and patchErrors() on a control as appropriate.
Validator functions
Validator functions are optional functions you can provide to a control which are run whenever the control’s value changes (technically rawValue) and either return null (if there are no errors) or return an object with key-value entries if there are errors.
import {typeValidatorFn}from"solid-forms";constrequiredValidator: ValidatorFn=(rawValue: string)=>rawValue.length===0 ? {isMissing: true} : null;constlowercaseValidator: ValidatorFn=(rawValue: string)=>rawValue.toLowerCase()!==rawValue ? {isNotLowercase: true} : null;// You can also create controls with validator functionsconstcontrol=createFormControl("",{validators: [requiredValidator,lowercaseValidator],});
In this example, we provide a validator function that returns an error if the control’s value is length 0 and a separate function which errors if the input string is not all lowercase. If we provide multiple validator functions to a control, all validator functions will be run on every change and their errors will be merged together.
You can update the validator function of a control with control.setValidators().
Observe changes and manually set errors
Validator functions are a nice and quick way to add validation to your controls, but a more powerful approach is to observe control changes and manually set errors. With this approach, we have access to the full array of control properties which we can include in our validation logic (validator functions just have access to the control’s value).
For example:
const control=createFormControl("");createRenderEffect(()=>{if(control.value.includes("@")){control.setErrors({mustNotIncludeSymbol: "Cannot include '@' symbol."});}else{control.setErrors(null);}});
Here we observe control value changes and set an error if the value includes an "@" symbol or else clear the errors (to indicate that the value is valid).
If we have multiple different validation effects, we should use the source property of control.setErrors() and control.patchErrors() to partition the errors associated with each effect.
const control=createFormControl("");createRenderEffect(()=>{constsource="@ validator";if(control.value.includes("@")){control.setErrors({mustNotIncludeSymbol: "Cannot include '@' symbol."},{ source });}else{control.setErrors(null,{ source });}});constsource=Symbol("Max length validator");createRenderEffect(()=>{if(control.value.length>10){control.setErrors({tooLong: "Cannot be more than 10 characters."},{ source });}else{control.setErrors(null,{ source });}});
Here when we use control.setErrors() and also provide the source option, we will only clear or overwrite existing errors that were also set by the same “source”. Another way we could have accomplished this would be by using control.patchErrors(). The patchErrors method merges its changes into the existing control.errors property, rather than replacing the existing errors. If you pass an errors object with a key who’s value is null, then that key will be deleted from the control errors.
For example:
createRenderEffect(()=>{if(control.value.includes("@")){control.patchErrors({mustNotIncludeSymbol: "Cannot include '@' symbol."});}else{control.patchErrors({mustNotIncludeSymbol: null});}});
However, in this case using control.patchErrors() isn’t as good an approach as using the “source” option. An accidental use of control.setErrors() elsewhere would still overwrite this render effect’s validation (which wouldn’t happen if you used “source”).
Also note, we’re using createRenderEffect() here (rather than createEffect()) since validation and errors are likely to affect the DOM (by displaying errors to the user).
Also note, when performing async validation you can mark a control as “pending” via control.markPending(true) to indicate that there is pending validation. control.isValid is only true if the control both doesn’t have any errors and is also not pending. The control.markPending() method also accepts a source option for partitioning the pending state of different validation effects. A control will be considered pending so long as any “source” is still pending.
To model a form (or fieldset) with multiple form fields, we’ll use FormGroups created via createFormGroup(). A FormGroup has all of the properties that a FormControl has, but it also has additional properties like a controls property which contains the FormGroup’s child controls.
At it’s simplest, we can create a FormGroup with no children and the default state.
If we want to add two child FormControls representing a person’s name to our FormGroup we could do the following (note, all control objects in solid-forms are immutable so you need to use the provided methods–e.g. setControl()–to update their state):
Note that FormGroup’s derive their value from the value of their enabled child controls. When you use group.setValue() you are really setting the values of the FormGroup’s children.
Since a FormGroup’s value is equal to the value of all their enabled children, is a child is disabled then it’s value will be excluded from group.value.
Here, we’re using a render effect to create a custom validator for the FormGroup. This effect is automatically subscribing to some changes under-the-hood (group.children.areValid, group.value.email, group.value.name) and will automatically re-evaluate when these props change. Lets look at what’s happening…
The FormGroup “child”, “children” and “self” props
You can view the full IFormGroup API reference for all of the self, children, and child properties.
Both FormControls and FormGroups (and FormArrays) have a self prop which itself is an object containing properties like isValid, isDisabled, isTouched, etc. In a FormControl, control.isValid is just an alias for control.self.isValid (same for control.isTouched, etc). In a FormGroup though, the two properties are different and the self object contains state local to that FormGroup.
For example, if you do group.markTouched(true) on the FormGroup, that updates group.self.isTouched to true but it doesn’t affect the state of the FormGroup’s children.
Meanwhile, group.isTouched is actually a getter function equal to group.self.isTouched || group.child.isTouched. The group.child.isTouched property is also a memoized getter (its created internally with createMemo()) which is true if any child is touched (it’s false if there are no children). So a FormGroup is “touched” if either the FormGroup itself has been markTouched(true) or if any of the FormGroup’s children have been markTouched(true). But if you just want to see if the FormGroup, itself, has been touched, then you can use group.self.isTouched. Meanwhile, group.children.areTouched will return true if all child controls have been touched. As expected, all of these properties are observable.
To the unfamiliar, this might look like we’re clearing all errors on the FormGroup if any children are invalid. In reality, group.errors is a getter function equal to { ...group.children.errors, ...group.self.errors } or null if there are no children errors or self errors. Using group.setErrors() sets self.errors but it doesn’t affect FormGroup children.
You can view the full IFormGroup API reference for all of the self, children, and child properties.
As you might expect, FormArrays are very similar to FormGroups and are used to group multiple controls together. While a FormGroup groups multiple controls together using an object with named keys, a FormArray groups them together using an array.
FormArrays behave very similarly to FormGroups but with the change that you’re dealing with an array of child controls (rather than an object containing child controls). FormArray also has one additional method push() which adds a new child control to the end of the FormArray. Other than push(), FormArray and FormGroup have the same interface (which comes from the IAbstractControlContainer interface). Read the FormGroup section if you haven’t yet since all of it also applies to FormArray.
import {For,typeComponent}from"solid-js";import{createFormArray,createFormControl,bindOwner}from"solid-forms";constExampleForm: Component<{}>=()=>{constarray=createFormArray([createFormControl("")]);constaddPhoneNumber=bindOwner(()=>array.push(createFormControl("")));constremovePhoneNumber=()=>array.removeControl(array.size-1);return(<form><div><p>Can you provide us with all of your phone numbers?</p></div><Foreach={array.controls}>{(control,index)=>{return(<><labelfor={index()}>Phone number {index()}</label><inputname={index()}value={control.value}oninput={(e)=>{control.setValue(e.currentTarget.value);}}onblur={()=>control.markTouched(true)}/></>);}}</For><buttononclick={addPhoneNumber}>Add phone number</button><buttononclick={removePhoneNumber}>Remove phone number</button><buttondisabled={!array.isValid}>Submit</button></form>);};
In this example, we have a form asking for all of the user’s phone numbers. Users can add or remove phone numbers from the form using buttons in the form (i.e. <button onclick={addPhoneNumber}>Add phone number</button>). As the user does this, callbacks will file which adds or removes FormControls from the FormArray. Since this is all observable, changes will be reflected in the component.
See the IFormArray API below for more information. Note, all control objects in solid-forms are immutable so you need to use the provided methods (e.g. IFormArray#push()) to update their state.
You may be wondering, what’s this bindOwner() function from “solid-forms” and why doesn’t removePhoneNumber() need it? (though if you’re a Solidjs wizz you may have already guessed). Solidjs’ reactivity has a concept of an “Owner” that you might never have run into before (the Solidjs “Getting Started” doc currently doesn’t mention it and it’s not something everyone will run into). The “Owner” is the parent of a given reactive context.
The tl;dr; is that, whenever you have a function that creates a control asyncronously, you need to wrap that function with bindOwner(). Here we’re creating a control inside a button’s onclick handler, which will be called asyncronously, so we need to use bindOwner(). We don’t need to do that for removePhoneNumber because that function is just removing an already created control from the FormArray (it’s not creating a new control).
For a longer (but still summarized) explaination of why this is needed and what an Owner is in Solidjs, here’s my attempt at a tl;dr for that (with the caviate that I’m not expert so this is really just my current guess)…
When call a component function (or something else using computations like createEffect()), it runs immediately. When it does so, Signals inside that component (or createEffect(), etc), call getOwner() and get the current owner of the context. They are able to do this because getOwner() is a global variable. The initial “root” Owner is set by the top level render() function which then calls the top level App component.
The first thing the App component does when it’s called is create a new Owner object for itself and set it to the global “owner” variable that is returned by getOwner(). Then the App component runs the actual component function you provided and when any Computations inside of it call getOwner() (and components are a type of Computation in solid) they receive the Owner associated with the App component. When the App component finishes executing the user provided function (which happens syncronously), then the App component resets the global “owner” viable to whatever it was before it started running. Any components nested inside the App component work the same way. As they are called, they create a new Owner object for themselves and replace the global owner with their own so that nested calls to getOwner() return this new Owner. When the child component function finishes executing, the original parent owner is restored. Etc.
So with this idea, immediately after a component is intialized the component’s getOwner() context is removed. If you call code asyncronously, for example inside a button onclick handler, when that code runs it can’t find the parent component’s owner anymore. The bindOwner() function provided by Solid Forms binds the component’s “Owner” context to a function so that, whenever that function is called (even asyncronously) it uses the right owner context. If you create a control asyncronously (e.g. inside of a button onclick handler) then you need to wrap that function with bindOwner(). The bindOwner() function that Solid Forms provides is just a super simple helper utility that looks like this:
import {getOwner,runWithOwner}from"solid-js";exportfunctionbindOwner<T>(fn: ()=>T): ()=>T{constowner=getOwner();if(!owner){thrownewError("No solidjs owner in current context");}return()=>runWithOwner(owner,fn);}
In the future, it seems possible that Solidjs will choose to provide this help directly within the core library.
Making reusable form components withControl()
To make it easier to build nice, reusable form components, Solid Forms provides an optional withControl() higher order component.
The withControl() function expects an object with controlFactory and component properties. The component property expects a component function. This component function will always receive a control prop that has the same typescript-type as the control returned by the provided controlFactory function. The control factory function is responsible for constructing the control used by the component. The control factory function optionally receives the component’s properties as arguments. When using the AddressField, it will have an optional control property. If you provide that property (like we did in the example above), then the controlFactory function will never be called. But withControl() allows us to use our AddressField by itself without providing a control property to it.
For example:
import {AddressField}from"./AddressField";exportconstApp: Component<{}>=()=>{return(<div><p>These are the questions we will ask you when we need your address.</p><AddressFieldrequired/></div>);};
This example will work just fine. In this case, withControl() will see that a control property wasn’t provided and will use the controlFactory function you gave to construct it’s control. Since we provided the optional required prop, that prop will be provided to the controlFactory function in the props param.
The controlFactory function, itself, operates like a component. It is only called once on initialization, and you can choose to use createEffect() and signals inside of it.
Finally, withControl() will add a control property containing your controlFactory function to the created Solidjs component. You can see this in action in the ParentComponent example, above
import {AddressField}from"./AddressField";exportconstParentForm: Component<{}>=()=>{constgroup=createFormGroup({firstName: createFormControl(""),lastName: createFormControl(""),// This use of `AddressField.control` is invoking the AddressField's// controlFactory functionaddress: AddressField.control({required: true}),});// ...};
const ExampleComponent: Component<{}>=()=>{constcontrol=createFormControl("",{// This optional validator function is run on every change.// If we return `null`, there are no errors. Else, we// can return an object containing errors.validators: (value: string)=>value.length===0 ? {isMissing: true} : null,});return(<div><labelfor="example">Please provide some text</label><inputname="example"type="text"value={control.value}oninput={(e)=>{control.setValue(e.currentTarget.value);}}onblur={()=>control.markTouched(true)}/><Showwhen={control.isTouched&&control.errors?.isMissing}><small>Answer required.</small></Show></div>);};
Alternatively, this is effectively the same as the above Stackblitz demo:
const ExampleComponent: Component<{}>=()=>{constcontrol=createFormControl("");// Under the hood, controls are just Solidjs stores// so every property is observable. Here we// observe the `value` prop and set or clear// errors as it changescreateRenderEffect(()=>{if(control.value.length>0){control.setErrors(null);}else{control.setErrors({isMissing: true});}});return(<div><labelfor="example">Please provide some text</label><inputname="example"type="text"value={control.value}oninput={(e)=>{control.setValue(e.currentTarget.value);}}onblur={()=>control.markTouched(true)}/><Showwhen={control.isTouched&&control.errors?.isMissing}><small>Answer required.</small></Show></div>);};
const ExampleForm: Component<{}>=()=>{constgroup=createFormGroup({name: createFormControl(""),email: createFormControl("",{required: true,validators: (value: string)=>!value.includes("@") ? {invalid: true} : null,}),});createRenderEffect(()=>{if(!group.children.areValid){group.setErrors(null);return;}constfirstPartOfEmail=group.value.email.split("@")[0];if(firstPartOfEmail!==group.value.name){group.setErrors({invalid: "email must match name"});}else{group.setErrors(null);}});return(<form><labelfor="name">Your name</label><TextInputname="name"control={group.controls.name}/><labelfor="email">Your email address</label><TextInputname="email"type="email"control={group.controls.email}/><button>Submit</button></form>);};
API
Note, all controls objects are immutable unless you use the provided methods to update their state.
IAbstractControl
interface IAbstractControl<RawValue=any,DataextendsRecord<ControlId,any>=Record<ControlId,any>,Value=RawValue>{/** * The ID is used to determine where StateChanges originated, * and to ensure that a given AbstractControl only processes * values one time. */readonlyid: ControlId;/** * The data property can store arbitrary custom data. Use the * `setData` method on `IAbstractControl` to update it. * * The `data` property is, itself, an object. You can set individual * keys on the data property with `setData` but you cannot reset * or clear the whole object. This is intentional. A library * maintainer can store private data within the `data` property * using a symbol without fear of the user accidently erasing it. */readonlydata: Data;/** * The value of the IAbstractControl. * * In an IAbstractControlContainer, * `value` and `rawValue` can be different, but in a standard * `IAbstractControl` `value` is just an alias for `rawValue`. * See the IAbstractControlContainer interface for possible differences * between `value` and `rawValue`. */readonlyvalue: Value;/** * The value of the IAbstractControl. * * In an IAbstractControlContainer, * `value` and `rawValue` can be different, but in a standard * `IAbstractControl` `value` is just an alias for `rawValue` and * rawValue just contains the control's value. * See the IAbstractControlContainer interface for possible differences * between `value` and `rawValue`. */readonlyrawValue: RawValue;/** * `true` if this control is disabled, false otherwise. * This is an alias for `self.isDisabled`. */readonlyisDisabled: boolean;/** * `true` if this control is touched, false otherwise. * This is an alias for `self.isTouched`. */readonlyisTouched: boolean;/** * `true` if this control is dirty, false otherwise. * This is an alias for `self.isDirty`. */readonlyisDirty: boolean;/** * `true` if this control is readonly, false otherwise. * This is an alias for `self.isReadonly`. */readonlyisReadonly: boolean;/** * `true` if this control is submitted, false otherwise. * This is an alias for `self.isSubmitted`. */readonlyisSubmitted: boolean;/** * `true` if this control is required, false otherwise. * This is an alias for `self.isRequired`. * * Note that this property doesn't * have any predefined meaning for IAbstractControls and it doesn't affect * validation in any way. It is up to you to decide what meaning, if any, * to give to this property and how to use it. For example, if you * validated the control inside a `createEffect()`, you could choose to alter the * validation based on whether the control was marked as `required` or * not. */readonlyisRequired: boolean;/** * Contains a `ValidationErrors` object if this control * has any errors. Otherwise contains `null`. * * An alias for `self.errors`. */readonlyerrors: ValidationErrors|null;/** * A validator function that is run on rawValue changes and which * generates errors associated with the source "CONTROL_DEFAULT_SOURCE". */readonlyvalidator: ValidatorFn|null;/** * `true` if this control is pending, false otherwise. * This is an alias for `self.isPending`. */readonlyisPending: boolean;/** * Valid if `errors === null && !isPending` * * This is an alias for `self.valid`. */readonlyisValid: boolean;/** * The `self` object on an abstract control contains * properties reflecting the control's personal state. On an * IAbstractControlContainer, the personal state can differ * from the control's state. For example, an * IAbstractControlContainer will register as disabled if * the control itself has been marked as disabled OR if * all of it's child controls are disabled. * * Marking the control container * itself as disabled doesn't mark the container's children as * disabled. On a standard IAbstractControl though, * the "self" properties are the same as regular properties. * I.e. `self.isInvalid` is the same as `isInvalid` on a * standard IAbstractControl (actually, `isInvalid` is * an alias for `self.isInvalid` on a standard control). */readonlyself: {/** `this.self.errors === null && !this.self.isPending` */readonlyisValid: boolean;/** `true` if this control is disabled, false otherwise. */readonlyisDisabled: boolean;/** `true` if this control is touched, false otherwise. */readonlyisTouched: boolean;/** * `true` if this control is dirty, false otherwise. * * Dirty can be thought of as, "Has the value changed?" * Though the isDirty property must be manually set by * the user (using `markDirty()`) and is not automatically * updated. */readonlyisDirty: boolean;/** * `true` if this control is readonly, false otherwise. * * This property does not have any predefined meeting for * an IAbstractControl. You can decide if you want to give * it meaning by, for example, using this value to set * an input's readonly status (e.g. * `<input readonly={control.isReadonly} />`) */readonlyisReadonly: boolean;/** `true` if this control is submitted, false otherwise. */readonlyisSubmitted: boolean;/** * `true` if this control is required, false otherwise. * * Note that this property doesn't * have any predefined meaning for IAbstractControls and it doesn't affect * validation in any way. It is up to you to decide what meaning, if any, * to give to this property and how to use it. For example, if you * validated the control inside a `createEffect()` you could alter the * validation based on whether the control was marked as `required` or * not. */readonlyisRequired: boolean;/** `true` if this control is pending, false otherwise. */readonlyisPending: boolean;/** * Contains a `ValidationErrors` object if this control * has any errors. Otherwise contains `null`. */readonlyerrors: ValidationErrors|null;/** * *More advanced-ish* * * Contains a map of ControlId values and ValidationErrors. * The errorsStore allows partitioning errors so that * they can be associated with different sources and so * that one source does not overwrite another source. * * The `self.errors` property gets its errors from the errorsStore. */readonlyerrorsStore: ReadonlyMap<ControlId,ValidationErrors>;/** * More advanced-ish* * * A set of ControlIds. `self.isPending` is true so long * as `pendingStore.size > 0`. Because this is a set, you * can track multiple pending "things" at once. This * control will register as pending until all of the "things" * have resolved. Use the `markPending()` method with * the `source` option to update the pendingStore. */readonlypendingStore: ReadonlySet<ControlId>;/** * More advanced-ish* * * A map of ControlIds and ValidatorFns. The `validator` * property is composed of all the validator functions in the * `validatorStore`. The validatorStore allows you to change * individual validator functions on the control without * affecting other validator functions on the control. * * When you use the `setValidators` method, you are updating * the validatorStore. */readonlyvalidatorStore: ReadonlyMap<ControlId,ValidatorFn>;};/** * If this control is disabled, the status is `"DISABLED"`, * else if this control is pending, the status is `"PENDING"`, * else if this control has any errors, the status is `"INVALID"`, * else the status is `"VALID"`. */readonlystatus: "DISABLED"|"PENDING"|"INVALID"|"VALID";[AbstractControlInterface]: true;/** set the control's value */setValue(value: RawValue): void;/** * If provided a `ValidationErrors` object or `null`, replaces `self.errors`. * Optionally, provide a source ID and the change will be partitioned * assocaited with the source ID. The default source ID is * "CONTROL_DEFAULT_SOURCE". * * If you provide a `Map` object containing `ValidationErrors` keyed to source IDs, * that will replace the `self.errorsStore` associated with this control. */setErrors(value: ValidationErrors|null|ReadonlyMap<ControlId,ValidationErrors>,options?: {source?: ControlId}): void;/** * If you provide a `ValidationErrors` object, that object is merged with the * existing errors associated with the source ID. If the error object has * keys equal to `null`, errors associated with those keys are deleted * from the errors object. * * If you provide a `Map` object containing `ValidationErrors` keyed to source IDs, * that object is merged with the existing `errorsStore`. */patchErrors(value: ValidationErrors|ReadonlyMap<ControlId,ValidationErrors>,options?: {source?: ControlId}): void;/** sets `self.isTouched` */markTouched(value: boolean): void;/** sets `self.isDirty` */markDirty(value: boolean): void;/** sets `self.isReadonly` */markReadonly(value: boolean): void;/** * Sets `self.isRequired`. * * Note that this property doesn't * have any predefined meaning for IAbstractControls and it doesn't affect * validation in any way. It is up to you to decide what meaning, if any, * to give to this property and how to use it. For example, if you * validated the control inside a `createEffect()` you could alter the * validation based on whether the control was marked as `required` or * not. */markRequired(value: boolean): void;/** * Set `self.isDisabled`. * * Note that `self.isDisabled`` affect's the control's `status` * property. Additionally, `IAbstractControlContainer's` ignore * disabled children in many cases. For example, the `value` of a * control container is equal to the value of it's _enabled_ children * (if you want to see the value including disabled children, use * `rawValue`). */markDisabled(value: boolean): void;/** sets `self.isSubmitted` */markSubmitted(value: boolean): void;/** sets `self.pendingStore` and `self.isPending` */markPending(value: boolean|ReadonlySet<ControlId>,options?: {source?: ControlId}): void;/** sets `validator` and `self.validatorStore` */setValidators(value:
|ValidatorFn|ValidatorFn[]|ReadonlyMap<ControlId,ValidatorFn>|null,options?: {source?: ControlId}): void;/** * The data property can store arbitrary custom data. Use the * `setData` method on `IAbstractControl` to update it. * * The `data` property is, itself, an object. You can set individual * keys on the data property with `setData` but you cannot reset * or clear the whole object. This is intentional. A library * maintainer can store private data within the `data` property * using a symbol without fear of the user accidently erasing it. */setData<KextendskeyofData>(key: K,data: Data[K]): void;}
IAbstractControlContainer
export interfaceIAbstractControlContainer<ControlsextendsGenericControlsObject=any,Data=any>extendsIAbstractControl<ControlsRawValue<Controls>,Data,ControlsValue<Controls>>{/** Child controls associated with this container */readonlycontrols: Controls;/** The number of controls associated with this container */readonlysize: number;/** Only returns values for enabled child controls. */readonlyvalue: ControlsValue<Controls>;/** * Returns values for both enabled and disabled child controls. */readonlyrawValue: ControlsRawValue<Controls>;/** Will return true if `this.self.isValid` and `this.children.areValid` */readonlyisValid: boolean;/** Will return true if `this.self.isDisabled` or `this.children.areDisabled` */readonlyisDisabled: boolean;/** Will return true if `this.self.isReadonly` or `this.children.areReadonly` */readonlyisReadonly: boolean;/** Will return true if `this.self.isRequired` or `this.child.isRequired` */readonlyisRequired: boolean;/** Will return true if `this.self.isPending` or `this.child.isPending` */readonlyisPending: boolean;/** Will return true if `this.self.isTouched` or `this.child.isTouched` */readonlyisTouched: boolean;/** Will return true if `this.self.isDirty` or `this.child.isDirty` */readonlyisDirty: boolean;/** Will return true if `this.self.isSubmitted` or `this.children.areSubmitted` */readonlyisSubmitted: boolean;/** Contains `{ ...this.children.errors, ...this.self.errors }` or `null` if there are none */readonlyerrors: ValidationErrors|null;readonlychild: {/** Will return true if *any* `enabled` direct child control is `valid` */readonlyisValid: boolean;/** Will return true if *any* direct child control is `disabled` */readonlyisDisabled: boolean;/** Will return true if *any* `enabled` direct child control is `readonly` */readonlyisReadonly: boolean;/** Will return true if *any* `enabled` direct child control is `required` */readonlyisRequired: boolean;/** Will return true if *any* `enabled` direct child control is `pending` */readonlyisPending: boolean;/** Will return true if *any* `enabled` direct child control is `touched` */readonlyisTouched: boolean;/** Will return true if *any* `enabled` direct child control is `dirty` */readonlyisDirty: boolean;/** Will return true if *any* `enabled` direct child control is `submitted` */readonlyisSubmitted: boolean;};readonlychildren: {/** Will return true if *all* `enabled` direct child control's are `valid` */readonlyareValid: boolean;/** Will return true if *all* direct child control's are `disabled` */readonlyareDisabled: boolean;/** Will return true if *all* `enabled` direct child control's are `readonly` */readonlyareReadonly: boolean;/** Will return true if *all* `enabled` direct child control's are `required` */readonlyareRequired: boolean;/** Will return true if *all* `enabled` direct child control's are `pending` */readonlyarePending: boolean;/** Will return true if *all* `enabled` direct child control's are `touched` */readonlyareTouched: boolean;/** Will return true if *all* `enabled` direct child control's are `dirty` */readonlyareDirty: boolean;/** Will return true if *all* `enabled` direct child control's are `submitted` */readonlyareSubmitted: boolean;/** Contains *all* `enabled` child control errors or `null` if there are none */readonlyerrors: ValidationErrors|null;/** * Mark all direct children as disabled. Use the `deep: true` * option to instead mark all direct and indirect children * as disabled. */markDisabled(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as touched. Use the `deep: true` * option to instead mark all direct and indirect children * as touched. */markTouched(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as dirty. Use the `deep: true` * option to instead mark all direct and indirect children * as dirty. */markDirty(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as readonly. Use the `deep: true` * option to instead mark all direct and indirect children * as readonly. */markReadonly(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as required. Use the `deep: true` * option to instead mark all direct and indirect children * as required. */markRequired(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as submitted. Use the `deep: true` * option to instead mark all direct and indirect children * as submitted. */markSubmitted(value: boolean,options?: {deep?: boolean}): void;/** * Mark all direct children as pending. Use the `deep: true` * option to instead mark all direct and indirect children * as pending. */markPending(value: boolean,options?: {source?: ControlId;deep?: boolean}): void;};[AbstractControlContainerInterface]: true;/** * Apply a partial update to the values of some children but * not all. */patchValue(value: unknown): void;/** sets the `controls` property */setControls(controls: Controls): void;/** stores the provided control in `controls[key]` */setControl(key: unknown,control: unknown): void;/** * If provided a control value, removes the given control from * `controls`. If provided a control key value, removes the * control associated with the given key from `controls`. */removeControl(key: unknown): void;}
IFormControl
See the IAbstractControl interface, above. IFormControl has the same properties as that interface. Like other solid-forms controls, form controls immutable unless you use the provided methods to update it’s state.
See the IAbstractControlContainer interface, above. IFormGroup has the same properties as that interface. Like other solid-forms controls, form groups immutable unless you use the provided methods to update it’s state.
See the IAbstractControlContainer interface, above. IFormArray has the same properties as that interface with one addtion: push() for adding new child controls to the end of the form array.Like other solid-forms controls, form arrays immutable unless you use the provided methods to update it’s state.
A higher order component function for creating reusable form components.
import {withControl}from"solid-forms";functionwithControl<Propsextends{},ControlFactoryextends(...args: [any, ...any[]])=>IAbstractControl>(options: IWithControlOptions<Props,ControlFactory>): WithControlReturnType<Props,ControlFactory>;interfaceIWithControlOptions<Propsextends{},ControlFactoryextends(...args: [any, ...any[]])=>IAbstractControl>{controlFactory: ControlFactory;component: Component<WithControlProps<Props,ControlFactory>&{control: ReturnType<ControlFactory>;}>;}typeWithControlReturnType<Propsextends{},ControlFactoryextends(...args: [any, ...any[]])=>IAbstractControl>=((props: WithControlProps<Props,ControlFactory>&{control?: ReturnType<ControlFactory>;})=>JSX.Element)&{/** * Factory function to build the component's default form control. * Note, you can pass any form control to the component which * satisfies the component's interface. You do not need to use * this factory function. * * Example usage: * ```ts * const TextInput = withControl({ * // etc... * }); * * createFormGroup({ * street: TextInput.control(), * city: TextInput.control(), * state: TextInput.control(), * zip: TextInput.control(), * }) * ``` */control: ControlFactory;};
bindOwner()
Helper to bind the owner of the current context to the supplied function.