Skip to main content

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);
});