Expandiendo el hook useFormik con objetos anidados en Typescript
Published: 2023-11-19 09:57:28

Cuando se esta desarrollando algún proyecto utilizando la librería se consulta en la documentación de esa librería con el objetivo de saber como se puede utilizar. Formik es una de las librerías más usadas para poder manejar los formularios en proyectos web. Ademas, se encarga de manejar las validaciones y el evento submit. Esta libería también cuenta para manejo de formularios, con inputs ya integrados con Formik y hooks en caso que se no se quiera usar dichos inputs. 

En la documentación con esa librería se cuentan con ejemplos para varíos casos usando sus inputs personalizados. Uno de ellos son objetos anidados demostrando soporte para este tipo de objetos. Si bien se evidencia el soporte de objetos anidados en caso de querer usar los hooks con los que cuenta la librería, no existen ejemplos. Por lo que aquí daremos apoyo a la duda de como se pueden integrar objetos anidados usando solo los hooks de la libería.

Todo el código utilizado para este tutorial lo podrás entrar aquí: [useFormik](https://github.com/andro095/Posts-Samples/tree/master/useFormik).

Para quien quiera una demo funcional: [useFormik demo](https://codesandbox.io/p/sandbox/blazing-glitter-xqqqcx?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clp4qm1eh000a3b5vf2d39oy3%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clp4qm1eh00033b5vo6gc6rvm%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clp4qm1eh00073b5vp1oe9obn%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clp4qm1eh00093b5v2s0jlujd%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B100%252C0%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clp4qm1eh00033b5vo6gc6rvm%2522%253A%257B%2522id%2522%253A%2522clp4qm1eh00033b5vo6gc6rvm%2522%252C%2522activeTabId%2522%253A%2522clp4tpzlh04vr3b5vm79488kh%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00023b5v3lvvto50%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.tsx%2522%252C%2522id%2522%253A%2522clp4qmfhs00633b5vhd3yduml%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.css%2522%252C%2522id%2522%253A%2522clp4qvdsg00p53b5voxmum94g%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Futils.ts%2522%252C%2522id%2522%253A%2522clp4tpzlh04vr3b5vm79488kh%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Ftypes.ts%2522%252C%2522id%2522%253A%2522clp543pn30ewv3b5vt5xvdfci%2522%252C%2522mode%2522%253A%2522temporary%2522%257D%255D%257D%252C%2522clp4qm1eh00093b5v2s0jlujd%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00083b5v97f1ia9x%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522port%2522%253A5173%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clp4qm1eh00093b5v2s0jlujd%2522%252C%2522activeTabId%2522%253A%2522clp4qm1eh00083b5v97f1ia9x%2522%257D%252C%2522clp4qm1eh00073b5vp1oe9obn%2522%253A%257B%2522id%2522%253A%2522clp4qm1eh00073b5vp1oe9obn%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00043b5vdny8p9lo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%257D%252C%257B%2522id%2522%253A%2522clp4qm1eh00053b5vdk85wkcc%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522build%2522%257D%252C%257B%2522id%2522%253A%2522clp4qm1eh00063b5vmpj354mx%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522preview%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clp4qm1eh00043b5vdny8p9lo%2522%257D%257D%252C%2522showDevtools%2522%253Afalse%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A24.907407407407405%257D)

### Valores Iniciales

Para empezar aclarando que en este ejemplo utilizaremos el hook useFormik pero, el ejemplo se puede aplicar con cualquier hook de la libería. Primero declaremos el objeto inicial con el que estaremos trabajando junto a su tipado.
```
interface  UserInfo  {
	personal:  {
		name:  string;
		lastName:  string;
		age:  number;
	};
	location:  {
		address:  string;
		country:  string;
	};
}

const  initialValues:  UserInfo  =  {
	personal:  {
		name:  "",
		lastName:  "",
		age:  0,
	},
	location:  {
		address:  "",
		country:  "",
	},
};
```

También inicializamos los validadores que utilizaremos para nuestro formulario. Para efectos de este ejemplo utilizaremos la libería de validadores Yup para facilitar validaciones.

```
const  validationSchema  =  yup.object().shape({
	personal:  yup.object().shape({
		name:  yup.string().required("Name is required"),
		lastName:  yup.string().required("Last name is required"),
		age:  yup
		.number()
		.required("Age is required")
		.min(1,  "You should have at least 1 year"),
	}),
	location:  yup.object().shape({
		address:  yup.string().required("Address is required"),
		country:  yup.string().required("Country is required"),
	}),
});
```

### Formik

Inicializamos el formik que vamos a utilizar para manejar nuestro formulario.

```
const  formik  =  useFormik<UserInfo>({
	initialValues,
	validationSchema,
	onSubmit:  (values, { resetForm })  =>  {
		console.log("values: ",  values);
		toast.current?.show({
			severity:  "success",
			summary:  "Form submitted",
			detail:  "The form was submitted",
		});
		resetForm();
	},
});
```

Con esto procederemos a explicar los inputs donde encontraremos la clave para utilizar objetos anidados 

### Inputs

Para evitar repetir código crearemos unos inputs especiales para que puedan ser útilizado en nuestro formulario de ejemplo. Aqui expongo uno de los tres tipos de input que crearemos para este formulario. Los otros dos los pueden encontrar en la [demo](https://codesandbox.io/p/sandbox/blazing-glitter-xqqqcx?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clp4qm1eh000a3b5vf2d39oy3%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clp4qm1eh00033b5vo6gc6rvm%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clp4qm1eh00073b5vp1oe9obn%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clp4qm1eh00093b5v2s0jlujd%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B100%252C0%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clp4qm1eh00033b5vo6gc6rvm%2522%253A%257B%2522id%2522%253A%2522clp4qm1eh00033b5vo6gc6rvm%2522%252C%2522activeTabId%2522%253A%2522clp4tpzlh04vr3b5vm79488kh%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00023b5v3lvvto50%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.tsx%2522%252C%2522id%2522%253A%2522clp4qmfhs00633b5vhd3yduml%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.css%2522%252C%2522id%2522%253A%2522clp4qvdsg00p53b5voxmum94g%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Futils.ts%2522%252C%2522id%2522%253A%2522clp4tpzlh04vr3b5vm79488kh%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Ftypes.ts%2522%252C%2522id%2522%253A%2522clp543pn30ewv3b5vt5xvdfci%2522%252C%2522mode%2522%253A%2522temporary%2522%257D%255D%257D%252C%2522clp4qm1eh00093b5v2s0jlujd%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00083b5v97f1ia9x%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522port%2522%253A5173%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clp4qm1eh00093b5v2s0jlujd%2522%252C%2522activeTabId%2522%253A%2522clp4qm1eh00083b5v97f1ia9x%2522%257D%252C%2522clp4qm1eh00073b5vp1oe9obn%2522%253A%257B%2522id%2522%253A%2522clp4qm1eh00073b5vp1oe9obn%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clp4qm1eh00043b5vdny8p9lo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%257D%252C%257B%2522id%2522%253A%2522clp4qm1eh00053b5vdk85wkcc%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522build%2522%257D%252C%257B%2522id%2522%253A%2522clp4qm1eh00063b5vmpj354mx%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522preview%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clp4qm1eh00043b5vdny8p9lo%2522%257D%257D%252C%2522showDevtools%2522%253Afalse%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A24.907407407407405%257D) o en el código subido al [repositorio](https://github.com/andro095/Posts-Samples/tree/master/useFormik).

```
<InputText
	id={name}
	name={name}
	value={get(formik.values,  name)}
	onChange={formik.handleChange}
	onBlur={formik.handleBlur}
	className={classNames({  "p-invalid":  isInvalid(name,  formik)  })}
/>
```

En esta clase de input podemos ver que la forma de detectar los cambios en el input es la misma forma que con los campos no anidados. Para obtener el valor solo basta con primero acceder al objeto anidado y luego al campo especifico. Formik para poder manejar los campos de objetos anidados y poder cambiarlos utiliza la siguiente sintaxis: "ob1.ob2.....objn.campo". Para poder acceder a su información solo basta con utilizar una función que permita con solo dar el objeto formik.values y el campo como la sintaxis descrita arriba traer el valor en cuestión. En este caso utilizamos la función get de la libería lodash para poder cumplir con dicha función.

### Resultado 

Luego de ver las partes que componen el formulario tendremos como resultado algo similar:

```
function  App()  {
	const  toast  =  useRef<Toast>(null);

	const  formik  =  useFormik<UserInfo>({
		initialValues,
		validationSchema,
		onSubmit:  (values, { resetForm })  =>  {
			console.log("values: ",  values);
			toast.current?.show({
				severity:  "success",
				summary:  "Form submitted",
				detail:  "The form was submitted",
			});
			resetForm();
		},
	});

	return  (
		<div  className="App">
		<Toast  ref={toast}  />
		<form
			className="w-full h-full flex flex-column align-items-center"
			onSubmit={formik.handleSubmit}
		>
			<h1>useFormik with nested objects</h1>
			<h2>Personal Info</h2>
			<div  className="flex flex-column gap-4">
				<MInputText  
					name="personal.name"  
					label="Name"  
					formik={formik}  
				/>
				<MInputText
					name="personal.lastName"
					label="Last name"
					formik={formik}
				/>
				<MInputNumber  
					name="personal.age" 
					label="Age" 
					formik={formik}
				/>
			</div>
			<h2>Address Info</h2>
			<div  className="flex flex-column gap-4">
			<MInputText  
				name="location.address"  
				label="Address"  
				formik={formik}  
			/>
			<MDropdown
				name="location.country"
				label="Country"
				formik={formik}
				options={countries}
			/>
			</div>
			<Button  
				label="Send Form"  
				type="submit"  
				className="mt-4"
			/>
		</form>
		</div>
	);
}
```

Por lo que usar objetos anidados con los hooks de useFormik no es complicado. Solo es de recordar que para editar los campos se utiliza la notación obj.campo y utilizar una función auxiliar para obtener los valores.

### Bibliografía
Formik: https://formik.org/docs/overview