Form dinamici con Formik
Tags: react, formik, form, typescript
Published: 2023-07-18 22:25:45

A volte si vorrebbe poter sfruttare parti comuni di un form usato per gestire più varianti della stessa entità o diverse entità e avere quindi delle parti dinamiche o condizionabili.
Immaginiamo per esempio un form per l'invio di ticket di due diverse nature ma gestiti dallo stesso endpoint; un esempio di modello potrebbe essere

```typescript
type Ticket = {
  type: "INFO",
  request: string,
  context: string,
} | {
  type: "ISSUE",
  request: string,
  description: string,
  part?: { name: string, code: string },
  priority: "H" | "M" | "L",
}
```
Il componente che gestisce la persistenza con l'API potrebbe avere questa struttura

```typescript
  const handleSubmit = useCallback((values: Ticket) => {
    console.log("API post request", values);
  }, []);

  const handleValidation = useCallback((values: Ticket) => {
    console.log("values on validation", values);
    // no error returned
    return {}
  }, []);

  const firstValues = useMemo<Ticket>(() => {
    let result: Ticket = {
      type: "INFO",
      request: "I'd like request info about...",
      context: "PRODUCTS"
    };

    if (props.ticketNature === "ISSUE") {
      result = {
        type: "ISSUE",
        request: "I've a problem with...",
        description: "",
        priority: "M",
        part: { code: "", name: "" },
      }
    }

    return result;
  }, [props]);


<Formik
  validate={handleValidation}
  onSubmit={handleSubmit}
  initialValues={firstValues}
  >
  {(formProps: FormikProps<any>) => (
    <>
      <p>{formProps.values.type === "INFO" ? "Info" : "Issue"} ticket</p>
      <Form>
        {props.ticketNature === "INFO" ? <InfoTicketInputs /> : <IssueTicketInputs />}
        <button type="submit" >Submit</button>
      </Form>
    </>
  )}
</Formik>
```

Il componente Formik, che fa da wrapper al form, ci permette di abbinare le funzioni di gestione **submit** e **validazione** e, con il metodo delle render prop, ci mette a disposizione proprietà utili per monitorare lo stato come `values`.
Il corpo del form invece può essere condizionabile e attraverso il contesto creato da Formik si possono recuperare valori e avere funzioni helper nei singoli componenti specifici. 

_Di seguito i componenti presentati sono privi di layout e label per focalizzarsi meglio sullo scopo del post._

Il primo componente per gestire i ticket di tipo INFO potrebbe essere semplicemente

```typescript
export function InfoTicketInputs() {
  return (
    <>
      <Field type="text" name="request" />
      <Field as="select" name="context">
        <option value="PRODUCTS">Products</option>
        <option value="ACCESSORIES">Accessories</option>
        <option value="CATALOGS">Catalogs</option>
      </Field>
    </>
  )
}
```
Sfruttiamo il componente Field di Formik che ci permette facilmente di mappare la prop da gestire tramite `name` e tipizzare il tipo di componente con `type`.

Il componente per gestire ticket di tipo ISSUE invece potrebbe avere una gestione dei campi dipendente da altro (l'esempio è assolutamente triviale) 

```typescript
export function IssueTicketInputs() {
  const { values, setFieldValue, getFieldProps } = useFormikContext<Ticket>();

  const handlePriority = useCallback((priorityValue: string) => {
    setFieldValue("priority", priorityValue);
    if (priorityValue === "H") {
      setFieldValue("description", `HIGH PRIORITY: ${getFieldProps("description").value}`)
    } else {
      setFieldValue("description", (getFieldProps("description").value as string).replace("HIGH PRIORITY: ", ""));
    }
  }, [values]);

  return (
    <>
      <Field type="text" name="request" />
      <Field type="text" name="description" />
      <Field type="text" name="part.name" />
      <Field type="text" name="part.code" />
      <Field as="select" name="priority" onChange={(e: any) => handlePriority(e.target.value)}>
        <option value="H">High</option>
        <option value="M">Medium</option>
        <option value="L">Low</option>
      </Field>
    </>
  )
}
```

Come si può notare l'hook `useFormikContext` permette di recuperare oltre ai valori attuali dello stato del form `values` anche di avere a disposizione degli helper, `setFieldValue` `getFieldProps` per recuperare o impostare i campi in modo programmatico.
Altra cosa da notare è che a name possiamo passare un **path** di prop per gestire valori annidati come nel caso di _part_.

Questo è solo un piccolo esempio di utilizzo di Formik ma utile a far intuire che nei casi in cui si devono gestire parecchi form torna molto utile sfruttare questa libreria.
E voi che ne pensate?

Potete trovare una demo in questo [repository](https://github.com/pix303/react-formik-demo)