apollo-form

Form state manager

Usage no npm install needed!

<script type="module">
  import apolloForm from 'https://cdn.skypack.dev/apollo-form';
</script>

README

Apollo-form

npm version downloads size Coverage Status dependencies Status type license Code style

Introduction

Advanced form state, optimized fields rendering, form validation, file picker and more.

Installation

npm i apollo-form
# or
yarn add apollo-form

Demo

Show code of stories

Demo sign in

Demo todo list

Demo with confirm

Examples

Full form

interface CreatePlanFormValues {
   title: string;
   price: number;
   features: Array<{ title: string }>;
}

const validationSchema = Yup.object().shape({
   title: Yup.string().required(),
   price: Yup.number()
      .required()
      .min(0),
   features: Yup.array().of(
      Yup.object().shape({
         title: Yup.string().required(),
      }),
   ),
});

const initialState = {
   title: '',
   price: 0,
   features: [],
};

function CreatePlanForm() {
   return (
      <Form<CreatePlanFormValues>
         name='CreatePlanForm'
         enableReinitialize
         initialState={initialState}
         validationSchema={validationSchema}
         onSubmit={async ({ values }, form) => {
            try {
               await createPlanMutation({ variables: values });

               form.reset();
            } catch (e) {
               form.responseError(e.message);
            }
         }}
      >
         <Field name='title'>{({ field }) => <input {...getFieldProps(field)} />}</Field>
         <Field name='price'>
            {({ field }) => <input type='number' {...getFieldProps(field)} />}
         </Field>

         <FieldArray<{ title: string }> name='features'>
            {({ field }) => {
               return (
                  <>
                     {field.value.map((el, i) => {
                        return (
                           <div key={'plan-feature-' + i}>
                              <input key={'test' + i} name={'features' + '.' + i} />
                           </div>
                        );
                     })}

                     <div>
                        <button onClick={() => field.push({ title: '' })}>Push feature</button>
                        <Button onClick={() => field.pop()}>Pop feature</Button>
                     </div>
                  </>
               );
            }}
         </FieldArray>

         <ResponseMessage>{({ error }) => <span>{error}</span>}</ResponseMessage>
         <FormLoader>
            {({ loading }) => (
               <span style={{ display: loading ? 'block' : 'none' }}>Loading...</span>
            )}
         </FormLoader>

         <Submit>
            {({ disabled }) => (
               <button type='submit' disabled={disabled}>
                  Create plan
               </button>
            )}
         </Submit>
         <Reset>
            {({ disabled }) => (
               <button type='reset' disabled={disabled}>
                  Reset
               </button>
            )}
         </Reset>
      </Form>
   );
}

create field

function FormTextField(props: { name: string; validate?: FieldValidator<string> }) {
   const field = useField(props);

   return <TextField {...getFieldProps(field)} />;
}

// or

function FormTextField(props: { name: string; validate?: FieldValidator<string> }) {
   return (
      <Field<string> name={props.name} validate={props.validate}>
         {({ field }) => {
            const showError = Boolean(!field.focused && field.touched && field.error);

            return (
               <TextField
                  value={field.value}
                  onChange={e => field.setFieldValue(e.target.value)}
                  onFocus={() => field.setFieldFocused()}
                  onBlur={() => field.setFieldTouched(true)}
                  helperText={showError ? field.error : undefined}
                  error={showError}
               />
            );
         }}
      </Field>
   );
}

create array field

function FormTextFieldArray(props: { name: string; validate: FieldValidator<string[]> }) {
   return (
      <FieldArray<string> name={props.name} validate={props.validate}>
         {({ field }) => {
            return (
               <>
                  {field.value.map((el, i) => {
                     return (
                        <Grid item xs={3} key={'arr-field' + i}>
                           <FormTextField
                              key={'test' + i}
                              name={props.name + '.' + i}
                              label={props.name + '.' + i}
                           />
                        </Grid>
                     );
                  })}

                  <Grid item xs={3}>
                     <Box display='flex'>
                        <Button onClick={() => field.push((field.value.length + 1).toString())}>
                           push
                        </Button>
                        <Button onClick={() => field.pop()}>pop</Button>
                     </Box>
                  </Grid>
               </>
            );
         }}
      </FieldArray>
   );
}

create file field

function FieldImage(props: Omit<ImageFieldProps, 'children'>) {
   return (
      <FieldFile accept={['image/jpeg', 'image/png']} maxSize={1024 * 500} {...props}>
         {({ field, onClick }) => {
            const [img, setImg] = React.useState<string | null>(null);

            React.useEffect(() => {
               if (field.value) {
                  fileToBase64(field.value).then(r => setImg(r));
               }
            }, [field.value]);

            return (
               <>
                  {field.value ? (
                     <>
                        {img && (
                           <>
                              <img style={{ width: '100%' }} src={img} alt={field.value.name} />
                              <Button onClick={onClick} variant='contained'>
                                 Upload new image
                              </Button>
                           </>
                        )}
                     </>
                  ) : (
                     <Button variant='contained' onClick={onClick}>
                        Upload image
                     </Button>
                  )}
               </>
            );
         }}
      </FieldFile>
   );
}

create submit button

function FormSubmit() {
   return (
      <Submit>
         {({ isValid, isSubmitted, loading, existsChanges }) => (
            <Button type='submit' disabled={loading || (isSubmitted ? !isValid : false)}>
               Submit
            </Button>
         )}
      </Submit>
   );
}

create reset button

function FormSubmit() {
   return (
      <Reset>
         {({ disabled }) => (
            <Button type='reset' disabled={disabled}>
               Submit
            </Button>
         )}
      </Reset>
   );
}

show error message

function FirstError(props: { name: string; ignoreTouched?: boolean }) {
   return (
      <ErrorMessage
         ignoreTouched={props.ignoreTouched}
         name={props.name}
         children={({ error }) => (
            <span>
               password-err: (<b style={{ color: 'red' }}>{error}</b>)
            </span>
         )}
      />
   );
}

show loader

function CustomLoader() {
   return (
      <FormLoader children={({ loading }) => <span>{loading ? 'loading...' : 'loaded'}</span>} />
   );
}

create query

const myFormQuery = makeApolloFormQuery('my-form');

function useMyFormState() {
    return useQuery(myFormQuery);
}

function Form() {
   return (
      <Form
         name='my-form'
         ...
      >
         ...
      </Form>
   );
}

Api

Form api

Name Type Required Description
name string yes graphql client value name, like this query Form { ${name} @client }
initialState object yes Initial form state
initialErrors FormErrors no Initial errors
initialTouches FormTouches no Initial touched fields
validationSchema ObjectSchema no Yup validation schema
validate Function no Custom form validation, function returned errors state
resetOnSubmit boolean no Reset form with initialState after submit
validateOnMount boolean no Validate form on mount
enableReinitialize boolean no Reset form with new initialState
onInit Function no Function for save form reference
onSubmit Function no Async function for handle form submit
onChange Function no Handle state changes (called only if values changed)
saveOnUnmount boolean no Save form state in apollo global state
resetOnUnmount boolean no Reset form with initialState after unmount form

Field api

Name Type Required Description
name string yes key of state object
validate Function no Function for validate value, return err(string) or undefined
children Function yes Function for render, return object of "value", "error", "touched", "focused" and change actions

License

MIT