Mano de entrada de datos y estados Mano de entrada de datos y estados

🥇 Mano de entrada de datos y estados

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…

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;
Estudio de componentes de Ionic Uso de dispositivos nativos en Ionic
comments powered by Disqus