movpushmov / effector-reform

make & use forms without a headache
https://movpushmov.dev/effector-reform/
MIT License
19 stars 3 forks source link

Dynamic Forms #13

Open movpushmov opened 3 months ago

movpushmov commented 3 months ago

Dynamic Forms RFC

Motivation

In some cases, businesses have requirements that fields in the form appear as a result of interaction with the user, therefore, they will not be known statically (in advance) and need to provide a powerful and user-friendly API to allow developers to create such forms.

New API's

createDynamicForm — creates dynamic form

function createDynamicForm<Schema>(
    options?: CreateFormOptions<Schema>
): DynamicFormType<Fields, Values, Errors>;

field, arrayField, fieldsGroup — for dynamic form schema pre-define

schema: fieldsGroup({
  name: field(),
  email: field(),
  relatives: arrayField(fieldsGroup({
    name: field(),
    passport: field(),
    favoriteTracks: arrayField(fieldsGroup({
      author: field(),
      name: field(),
    }))
  })),
})

DynamicForm — dynamic form type

interface DynamicFormType<Fields, Values, Errors> {
    $fields: Store<Fields>;
    $values: Store<Values>;
    $errors: Store<Errors>;

    $isValid: Store<boolean>;
  $isDirty: Store<boolean>;

  $isValidationPending: Store<boolean>;

  setValues: EventCallable<Values>;
  setPartialValues: EventCallable<PartialRecursive<Values>>;

  setErrors: EventCallable<ErrorsSchemaPayload>;

  changed: EventCallable<Values>;
  errorsChanged: Event<Errors>;

  validate: EventCallable<void>;
  validated: Event<Values>;
  validatedAndSubmitted: Event<Values>;

  submit: EventCallable<void>;
  submitted: Event<Values>;

  reset: EventCallable<void>;
  clear: EventCallable<void>;

  get<Result extends PrimitiveField | ArrayField>(path: string): Result | undefined;

    addGroup: EventCallable<{ path: string }>;
    addPrimitiveField: EventCallable<{ path: string, fieldOptions: PrimitiveFieldOptions }>;
    addArrayField: EventCallable<{ path: string, fieldOptions: ArraFieldOptions }>;

    remove: EventCallable<{ path: string }>;

  metaChanged: EventCallable<{ fieldPath: string; meta: any }>;

  '@@unitShape': () => {
    values: Store<Values>;
    errors: Store<Errors>;

    isValid: Store<boolean>;
    isDirty: Store<boolean>;
    isValidationPending: Store<boolean>;

    submit: EventCallable<void>;
    validate: EventCallable<void>;

    reset: EventCallable<void>;
    clear: EventCallable<void>;

    setValues: EventCallable<Values>;
    setErrors: EventCallable<ErrorsSchemaPayload>;

    setPartialValues: EventCallable<PartialRecursive<Values>>;
  };
}

API changes

FormType — add get method

Now, in static form, it will also be possible to get a field by key immediately with typings

get<Path extends FormPaths>(path: Path): FormFieldFromPath<Form, Path>;

Examples

const form = createDynamicForm({ schema: {} });

function App() {
  const { fields, addPrimitiveField } = useDynamicForm(form);

  return (
    <>
      <p>fields list</p>

      {Object.values(fields).map((field) => (
          <input
            onChange={(e) => field.onChange(e.currentTarget.value)}
            value={field.value}
          />
      ))}

      <button onClick={() => addPrimitiveField({ path: Math.random().toString(), value: '' })}>
        add field
      </button>
    </>
  );
}