Objetivos de la lección
Durante el siguiente módulo nos enfocaremos en el trabajo de control de estados mediante React. Para ello vamos a capturar datos mediante campos de entrada, y con ellos vamos a controlar dichos estados.
¿Cómo validar datos con Ionic?
Hasta este punto podemos crear nuevos cursos agregando un título y una fecha de inicio. Este se puede ver dentro del componente AgregarCurso
.
<IonGrid>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">Titulo del curso</IonLabel>
<IonInput type="text" />
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">Fecha</IonLabel>
<IonDatetime displayFormat="MM DD YY" />
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonButton fill="clear" onClick={props.onClickCancelar}>
Cancelar
</IonButton>
</IonCol>
<IonCol>
<IonButton color="primary">Guardar</IonButton>
</IonCol>
</IonRow>
</IonGrid>
Sin embargo si deseamos cerciorarnos que los valores enviados desde el formularios tengan un valor asignado, requerimos realizar una validación de dichos valores.
Importamos los hooks useRef
y useState
del paquete React
, y agregamos las referencias a los campos así como un estado error
tipo string para el control de errores.
const [error, setError] = useState<string>("");
const tituloRef = useRef<HTMLIonInputElement>(null);
const fechaRef = useRef<HTMLIonDatetimeElement>(null);
Adicionalmente tenemos que agregar los controles ref
a cada campo.
<IonGrid>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">Titulo del curso</IonLabel>
<IonInput type="text" ref={tituloCapturado} />
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonItem>
<IonLabel position="floating">Fecha</IonLabel>
<IonDatetime displayFormat="MM DD YY" ref={fechaCapturada} />
</IonItem>
</IonCol>
</IonRow>
<IonRow>
<IonCol>
<IonButton fill="clear" onClick={props.onClickCancelar}>
Cancelar
</IonButton>
</IonCol>
<IonCol>
<IonButton color="primary">Guardar</IonButton>
</IonCol>
</IonRow>
</IonGrid>
Una ves completado lo anterior, requerimos también agregan una función que guarde controle el proceso de la validación.
const onClickGuardar = () => {
const tituloCapturado = tituloRef.current!.value;
const fechaCapturada = fechaRef.current!.value;
if (
!tituloCapturado ||
!fechaCapturada ||
tituloCapturado.toString().trim().length == 0 ||
fechaCapturada.trim().length == 0
) {
setError("Ingresa un título y una fecha valida");
return;
}
setError("");
};
De esta forma cuando alguno de los dos campos sea invalido, el control de estado error
mostrará un mensaje.
Agregamos este mensaje justo debajo de los campos de entrada.
{
error && (
<IonRow class="ion-text-center">
<IonCol>
<IonText color="danger">
<p>{error}</p>
</IonText>
</IonCol>
</IonRow>
);
}
Finalmente para disparar el evento enlazamos la acción al botón Guardar
.
<IonButton color="primary" onClick={onClickGuardar}>
Guardar
</IonButton>
¿Cómo enviar datos al modal en Ionic?
Creamos un contenedor para el context
en React src/data/cursos-context.ts.
, un contenedor nos permite definir estados en un nivel superior de la jerarquía de los componentes.
import React from "react";
export interface Objetivo {
id: string;
text: string;
}
export interface Curso {
id: string;
titulo: string;
fecha: Date;
objetivos: Objetivo[];
}
export interface Context {
cursos: Curso[];
agregarCurso: (titulo: string, fecha: Date) => void;
agregarObjetivo: () => void;
borrarObjetivo: () => void;
actualizarObjetivo: () => void;
}
const CursosContext = React.createContext<Context>({
cursos: [],
agregarCurso: () => {},
agregarObjetivo: () => {},
borrarObjetivo: () => {},
actualizarObjetivo: () => {},
});
export default CursosContext;
Nuestro contenedor consta de…
- Las interfaces Objetivo y Curso que forman parte de la estructura de datos de la interface
Context
. - La interface
Context
tiene ademas un arreglo llamadocursos
que almacena objetos tipoCurso
. - Los métodos para el control de los estados.
Ahora para implementar el Context
que acabamos de crear requerimos de un ContentProvider
este se va a encargar de controlar los estados.
import React, { useState } from "react";
import CursosContext, { Curso } from "./cursos-context";
const CursosContextProvider: React.FC = (props) => {
const [cursos, setCursos] = useState<Curso[]>([]);
const agregarCurso = (titulo: string, fecha: Date) => {
const nuevoCurso: Curso = {
id: Math.random().toString(),
titulo: titulo,
fecha: fecha,
objetivos: [],
};
setCursos((estadoActual) => {
return estadoActual.concat(nuevoCurso);
});
};
const agregarObjetivo = () => {};
const borrarObjetivo = () => {};
const actualizarObjetivo = () => {};
return (
<CursosContext.Provider
value={{
cursos,
agregarCurso,
agregarObjetivo,
borrarObjetivo,
actualizarObjetivo,
}}
>
{props.children}
</CursosContext.Provider>
);
};
export default CursosContextProvider;
Para ello implementamos el CursosContextProvider
dentro de la parte superior de nuestra aplicación, en este caso de App
.
import CursosContextProvider from "./data/CursosContextProvider";
const App: React.FC = () => (
<IonApp>
<IonReactRouter>
...
<IonTabs>
<IonRouterOutlet id="main">
<CursosContextProvider>
<Route path="/cursos" exact>
<Cursos />
</Route>
<Route path="/objetivos" exact>
<Objetivos />
</Route>
<Route path="/filtrar" exact>
<Filtrado />
</Route>
<Route path="/curso/:id">
<Objetivo />
</Route>
<Redirect to="/cursos" />
</CursosContextProvider>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="a" href="/objetivos">
<IonLabel>Objetivos</IonLabel>
</IonTabButton>
<IonTabButton tab="b" href="/cursos">
<IonLabel>Cursos</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
</IonApp>
);
Actualizamos el módulo AgregarCurso
para hacer uso del CursosContext
.
import React, { useRef, useState } from "react";
...
const AgregarCurso: React.FC<{
esVisible: boolean;
onClickCancelar: () => void;
onGuardar: (titulo: string, fecha: Date) => void;
}> = (props) => {
const [error, setError] = useState<string>("");
const tituloRef = useRef<HTMLIonInputElement>(null);
const fechaRef = useRef<HTMLIonDatetimeElement>(null);
const onClickGuardar = () => {
setError("");
const tituloCapturado = tituloRef.current!.value;
const fechaCapturada = fechaRef.current!.value;
if (
!tituloCapturado ||
!fechaCapturada ||
tituloCapturado.toString().trim().length === 0 ||
fechaCapturada.toString().trim().length === 0
) {
setError("Ingresa un título y una fecha valida");
return;
}
props.onGuardar(
tituloCapturado.toString().trim(),
new Date(fechaCapturada)
);
};
...
Al igual que implementamos el context
dentro de Cursos
.
import React, { useContext, useState } from "react";
...
import CursosContext from "../data/cursos-context";
...
const Cursos: React.FC = () => {
const [agregarCursoOverlayVisible, setAgregarCursoOverlayVisible] = useState<boolean>(false);
const cursosCtx = useContext(CursosContext);
const onClickAgregarCurso = () => {
setAgregarCursoOverlayVisible(true);
};
const onCancelarAgregarCurso = () => {
setAgregarCursoOverlayVisible(false);
};
const onGuardarCurso = (titulo: string, fecha: Date) => {
setAgregarCursoOverlayVisible(false);
cursosCtx.agregarCurso(titulo, fecha);
};
return (
<React.Fragment>
<AgregarCurso
esVisible={agregarCursoOverlayVisible}
onClickCancelar={onCancelarAgregarCurso}
onGuardar={onGuardarCurso}
/>
<IonPage>
...
<IonContent>
<IonGrid>
{cursosCtx.cursos.map((curso) => {
return (
<IonRow key={curso.id}>
<IonCol>
<IonCard>
<IonCardHeader>
<IonCardTitle>{curso.titulo}</IonCardTitle>
<IonCardSubtitle>{curso.fecha.toTimeString()}</IonCardSubtitle>
</IonCardHeader>
<IonCardContent>
<div className="ion-text-right">
<IonButton
fill="clear"
color="secondary"
routerLink={`/curso/${curso.id}`}
>
Ver Información
</IonButton>
</div>
</IonCardContent>
</IonCard>
</IonCol>
</IonRow>
);
})}
</IonGrid>
...
</IonContent>
</IonPage>
</React.Fragment>
);
};
export default Cursos;