Sub form
formo provides a simple way to create subforms.
A subForm is a nested form which requires its own validations.
The typical use case is when you have a field which is a variable list of complex elements.
For example, let's say you have a form asking the user their name, surname and familyMembers. The familyMembers, in turn, have their own fields (name and surname) that need to be validated before being added to the user's field array.
tsximport {useFormo ,subFormValue ,success ,validators } from "@buildo/formo";ÂtypeFamilyMember = {name : string;surname : string;};Âexport constMyForm = () => {const {handleSubmit ,fieldProps ,subForm ,formErrors } =useFormo ({initialValues : {name : "Jhon",surname : "Doe",familyMembers :subFormValue <Array <FamilyMember >>([]),},fieldValidators : () => ({}),subFormValidators : () => ({familyMembers : {name :validators .minLength (1, "Family member name is required"),surname :validators .minLength (1, "Family member surname is required"),},}),},{onSubmit : () =>Promise .resolve (success (null)),});ÂconstemptyFamilyMember = {name : "",surname : "",};Âreturn (<div ><TextField label ="name" {...fieldProps ("name")} /><TextField label ="surname" {...fieldProps ("surname")} />Â<div ><button onClick ={() =>subForm ("familyMembers").push (emptyFamilyMember )}>Add family members</button >Â{subForm ("familyMembers").items .map ((familyMember ,index ) => (<div key ={`${familyMember .namePrefix }-container`}><TextField label ={`${index }-name`}{...familyMember .fieldProps ("name")}/><TextField label ={`${index }-surname`}{...familyMember .fieldProps ("surname")}/><p >{familyMember .fieldProps ("name").issues }<br />{familyMember .fieldProps ("surname").issues }</p ></div >))}</div >Â<button onClick ={handleSubmit }>Sign up</button ></div >);};
tsximport {useFormo ,subFormValue ,success ,validators } from "@buildo/formo";ÂtypeFamilyMember = {name : string;surname : string;};Âexport constMyForm = () => {const {handleSubmit ,fieldProps ,subForm ,formErrors } =useFormo ({initialValues : {name : "Jhon",surname : "Doe",familyMembers :subFormValue <Array <FamilyMember >>([]),},fieldValidators : () => ({}),subFormValidators : () => ({familyMembers : {name :validators .minLength (1, "Family member name is required"),surname :validators .minLength (1, "Family member surname is required"),},}),},{onSubmit : () =>Promise .resolve (success (null)),});ÂconstemptyFamilyMember = {name : "",surname : "",};Âreturn (<div ><TextField label ="name" {...fieldProps ("name")} /><TextField label ="surname" {...fieldProps ("surname")} />Â<div ><button onClick ={() =>subForm ("familyMembers").push (emptyFamilyMember )}>Add family members</button >Â{subForm ("familyMembers").items .map ((familyMember ,index ) => (<div key ={`${familyMember .namePrefix }-container`}><TextField label ={`${index }-name`}{...familyMember .fieldProps ("name")}/><TextField label ={`${index }-surname`}{...familyMember .fieldProps ("surname")}/><p >{familyMember .fieldProps ("name").issues }<br />{familyMember .fieldProps ("surname").issues }</p ></div >))}</div >Â<button onClick ={handleSubmit }>Sign up</button ></div >);};
Let's break down the code above.
Define a sub form#
The subFormValue function is used to initialize a subform: this instructs formo to treat the field as a sub form, rather than a regular top-level field.
If the initial state is an empty array you will need to provide a type hint, since TypeScript won't be able to infer the type for you, for example:
tssubFormValue ([] asArray <FamilyMember >);
tssubFormValue ([] asArray <FamilyMember >);
otherwise, you can explicitly define the type of the assigned constant
tsconstfamilyMembersInitialState :Array <FamilyMember > = [];subFormValue (familyMembersInitialState );
tsconstfamilyMembersInitialState :Array <FamilyMember > = [];subFormValue (familyMembersInitialState );
subFormValidators work the same as fieldValidators, except they are applied to each of the members of the "sub form" fields.
tsconst {handleSubmit ,fieldProps ,subForm ,formErrors } =useFormo ({initialValues : {name : "Jhon",surname : "Doe",familyMembers :subFormValue <Array <FamilyMember >>([]),},fieldValidators : () => ({}),subFormValidators : () => ({familyMembers : {name :validators .fromPredicate ((i ) => typeofi === "string" &&i .length > 0,"Family member name is required"),surname :validators .fromPredicate ((i ) => typeofi === "string" &&i .length > 0,"Family member surname is required"),},}),},{onSubmit : () =>Promise .resolve (success (null)),});Â
tsconst {handleSubmit ,fieldProps ,subForm ,formErrors } =useFormo ({initialValues : {name : "Jhon",surname : "Doe",familyMembers :subFormValue <Array <FamilyMember >>([]),},fieldValidators : () => ({}),subFormValidators : () => ({familyMembers : {name :validators .fromPredicate ((i ) => typeofi === "string" &&i .length > 0,"Family member name is required"),surname :validators .fromPredicate ((i ) => typeofi === "string" &&i .length > 0,"Family member surname is required"),},}),},{onSubmit : () =>Promise .resolve (success (null)),});Â
Accessing sub forms#
Note how subForm and fieldProps statically enforce the correct field names: for example, you can't accidentally call subForm("surname")
tsArgument of type '"name"' is not assignable to parameter of type '"familyMembers"'.2345Argument of type '"name"' is not assignable to parameter of type '"familyMembers"'.subForm ("name" );Argument of type '"surname"' is not assignable to parameter of type '"familyMembers"'.2345Argument of type '"surname"' is not assignable to parameter of type '"familyMembers"'.subForm ("surname" );ÂsubForm ("familyMembers");
tsArgument of type '"name"' is not assignable to parameter of type '"familyMembers"'.2345Argument of type '"name"' is not assignable to parameter of type '"familyMembers"'.subForm ("name" );Argument of type '"surname"' is not assignable to parameter of type '"familyMembers"'.2345Argument of type '"surname"' is not assignable to parameter of type '"familyMembers"'.subForm ("surname" );ÂsubForm ("familyMembers");
You can add new sub forms to a field using push or insertAt:
tsxsubForm ("familyMembers").insertAt ;subForm ("familyMembers").push ;
tsxsubForm ("familyMembers").insertAt ;subForm ("familyMembers").push ;
For example, to add a new family member with an initial state, you can use the push method inside a button onClick
tsxconstemptyFamilyMember = {name : "",surname : "",};Â<button onClick ={() =>subForm ("familyMembers").push (emptyFamilyMember )}>Add family member</button >;
tsxconstemptyFamilyMember = {name : "",surname : "",};Â<button onClick ={() =>subForm ("familyMembers").push (emptyFamilyMember )}>Add family member</button >;
To access all the elements of a sub form field, use the items property
tssubForm ("familyMembers").items ;
tssubForm ("familyMembers").items ;
Each of the items provides a fieldProps function analogous to the top-level fieldProps, that can be used to render a form element:
tsxsubForm ("familyMembers").items .map ((familyMember ,index ) => (<div ><TextField key ={`${familyMember .namePrefix }-name`}label ={`${index }-name`}{...familyMember .fieldProps ("name")}/><TextField key ={`${familyMember .namePrefix }-surname`}label ={`${index }-surname`}{...familyMember .fieldProps ("surname")}/><p >{familyMember .fieldProps ("name").issues }<br ></br >{familyMember .fieldProps ("surname").issues }</p ></div >));
tsxsubForm ("familyMembers").items .map ((familyMember ,index ) => (<div ><TextField key ={`${familyMember .namePrefix }-name`}label ={`${index }-name`}{...familyMember .fieldProps ("name")}/><TextField key ={`${familyMember .namePrefix }-surname`}label ={`${index }-surname`}{...familyMember .fieldProps ("surname")}/><p >{familyMember .fieldProps ("name").issues }<br ></br >{familyMember .fieldProps ("surname").issues }</p ></div >));