FormContext
A React component that provides a more in-depth context of the "Form".
Props
onInit
: function
- A function invoked when the Form is initialized.
const onInit = (formState, isFormValid) => { // some operation }
onChange
: function
- A function invoked when any Form Field changes its value.
const onChange = (formState, isFormValid) => { // some operation }
onReset
: function
- A function invoked when the form has been reset to its initial State.
const onReset = (formState, isFormValid) => { // some operation }
onSubmit
: function
- A function invoked when the submit button has been pressed.
- The function may return either a Promise or a boolean value of true/false.
const onSubmit = (formState) => { // some operation };
const onSubmit = (formState) => new Promise((resolve, reject) => { // some async operation });
- Cases:
- If the function returns a Promise which is resolved, it will increment the value named submitted.
- If the function returns a boolean value
true
, or no return at all, it will increment the value named submitted. - If the function returns a Promise which is rejected, the value named submitted will not be incremented.
- If the function returns a boolean value
false
, the value named submitted will not be incremented.
const { submitted, submitAttempts } = useForm();
- It will be only invoked if your form passes all validations added at any level (Collections or Fields).
- For each invocation, the value submitAttempts will be incremented.
initialState
: object
- It is a plain object that represents the initial state of the form.
reducers
: array | function
(nextState, prevState) => nextState
- An array whose values correspond to different reducer functions.
- Reducer functions specify how the Form's state changes.
touched
: boolean
-
Default value of false.
-
If true, sync validation messages will be shown but only when the event onBlur of any forms's field is triggered by a user action at any level of nesting.
Basic usage
import { FormContext, Input, Collection, useSelector } from 'usetheform'
import { Form } from './MyFormWithContext.ts'
Preview
Live Editor
function FormContextBasic() { const Tags = () => { const [tags = [], setTags] = useSelector(state => state.tags); const resetTag = target => setTags(prev => prev.filter((val) => val !== target)); return tags.map((tag, index) => ( <button type="button" style={{ backgroundColor: tag }} key={tag} onClick={() => resetTag(tag)}> Uncheck {tag} Tag </button> )); } const onSubmit = (state) => alert(JSON.stringify(state)); return ( <FormContext onSubmit={onSubmit}> <Form> <Collection array name="tags" as="div" className="flex space-x-4"> <Input type="checkbox" value="Blue" checked placeholder="Tag Blue" /> <Input type="checkbox" value="Red" placeholder="Tag Red" /> <Input type="checkbox" value="Pink" placeholder="Tag Pink" /> </Collection> <button type="submit">Submit</button> </Form> <div className="flex space-x-4"> <Tags /> </div> </FormContext> ); }
MyFormWithContext.ts
import { useForm } from 'usetheform'
export const Form = ({ children }) => {
const { onSubmitForm } = useForm();
return (
<form onSubmit={onSubmitForm}>
{children}
</form>
);
};
Reducers
import { FormContext, Input } from 'usetheform'
Preview
Live Editor
function FormContextWithReducers() { const maxNumber10 = (nextState, prevState) => { if (nextState.myNumber > 10) { nextState.myNumber = 10; } return nextState; }; const minNumber1 = (nextState, prevState) => { if (nextState.myNumber <= 1) { nextState.myNumber = 1; } return nextState; }; return ( <FormContext reducers={[minNumber1, maxNumber10]}> <Form> <Input type="number" name="anyNumber" value="1" placeholder="Number" /> </Form> </FormContext> ) }
Validation - Sync
Validation at FormContext level:
- touched=false: error messages will be shown on FormContext initialization and when any Field is edited.
- touched=true: error messages will be shown when any Field at any level of nesting is touched/visited.
import { FormContext, Input, Collection, useValidation } from 'usetheform'
Preview
Live Editor
function FormContextSyncValidation(){ const [status, validationProps] = useValidation([({ values }) => ((values && (values["A"] + values["B"] > 10)) ? undefined : "A+B must be > 10")]); return ( <FormContext touched {...validationProps}> <Form> <Collection object name="values" > <Input type="number" name="A" placeholder="Number A" value="1" /> <Input type="number" name="B" placeholder="Number B" value="2" /> </Collection> {status.error && <label className="vl">{status.error}</label>} <button type="submit">Press to see results</button> </Form> </FormContextX> ) }
Validation - Async
Async Validation for FormContext is triggered on the Submit event. The form submission is prevented if the validation fails. This means that the onSubmit function passed as a prop to the FormContext component will not be invoked.
import { FormContext, Collection, Input, useAsyncValidation } from 'usetheform';
Preview
Live Editor
function FormContextAsyncValidation() { const [asyncStatus, validationProps] = useAsyncValidation(asyncTestForm); const onSubmit = (state) => alert(JSON.stringify(state)); return ( <FormContext onSubmit={onSubmit} {...validationProps}> <Form> <Collection object name="values"> <Input type="number" name="a" placeholder="Number A" value="1" /> <Input type="number" name="b" placeholder="Number B" value="2" /> </Collection> {asyncStatus.status === undefined && <label className="vl">Async Check Not Started Yet</label>} {asyncStatus.status === "asyncStart" && <label className="vl">Checking...</label>} {asyncStatus.status === "asyncError" && <label className="vl">{asyncStatus.value}</label>} {asyncStatus.status === "asyncSuccess" && <label className="vl">{asyncStatus.value}</label>} <Submit /> </Form> </FormContext> ) }
Detailed Explanation:
Submit.ts
import { useForm } from 'usetheform'
const Submit = () => {
const { isValid } = useForm();
return (
<button disabled={!isValid} type="submit">
Submit
</button>
);
};
AsyncValidations.ts
export const asyncTestForm = ({ values }) =>
new Promise((resolve, reject) => {
// it could be an API call or any async operation
setTimeout(() => {
if (!values || !values.a || !values.b) {
reject("Emtpy values are not allowed ");
}
if (values.a + values.b >= 5) {
reject("The sum must be less than '5'");
} else {
resolve("Success");
}
}, 1000);
});