Saltar al contenido

b

Conceptos básicos de React Native

Ahora que hemos configurado nuestro entorno de desarrollo podemos adentrarnos en los conceptos básicos de React Native y comenzar con el desarrollo de nuestra aplicación. En esta sección, aprenderemos cómo construir interfaces de usuario con los componentes centrales de React Native, cómo agregar propiedades de estilo a estos componentes centrales, cómo hacer la transición entre vistas y cómo administrar el estado del formulario de manera eficiente.

Componentes principales

En las partes anteriores, hemos aprendido que podemos usar React para definir componentes como funciones que reciben props como argumento y devuelven un árbol de elementos React. Este árbol generalmente se representa con sintaxis JSX. En el entorno del navegador, hemos utilizado la biblioteca ReactDOM para convertir estos componentes en un árbol DOM que puede ser renderizado por un navegador. Aquí hay un ejemplo concreto de un componente muy simple:

import React from 'react';

const HelloWorld = props => {
  return <div>Hello world!</div>;
};

El componente HelloWorld devuelve un único elemento div que se crea utilizando la sintaxis JSX. Podríamos recordar que esta sintaxis JSX se compila en llamadas al método React.createElement, como esta:

React.createElement('div', null, 'Hello world!');

Esta línea de código crea un elemento div sin ningún prop y con un solo elemento hijo que es una cadena "Hello World". Cuando renderizamos este componente en un elemento DOM raíz usando el método ReactDOM.render, el elemento div se renderizará como el elemento DOM correspondiente.

Como podemos ver, React no está vinculado a un entorno determinado, como el entorno del navegador. En cambio, existen bibliotecas como ReactDOM que pueden representar un conjunto de componentes predefinidos, como elementos DOM, en un entorno específico. En React Native, estos componentes predefinidos se denominan componentes principales.

Los componentes principales son un conjunto de componentes proporcionados por React Native que, entre bastidores, utilizan los componentes nativos de la plataforma. Implementemos el ejemplo anterior usando React Native:

import React from 'react';
import { Text } from 'react-native';
const HelloWorld = props => {
  return <Text>Hello world!</Text>;};

Así que importamos el componente Text de React Native y reemplazamos el elemento div con un elemento Text . Muchos elementos DOM familiares tienen sus "contrapartes" de React Native. Aquí hay algunos ejemplos seleccionados de la documentación de los componentes principales de React Native:

  • El componente Texto es el único componente de React Native que puede tener hijos textuales. Es similar, por ejemplo, a los elementos <strong> y <h1>.
  • El componente View es el componente básico de la interfaz de usuario similar al <div>
  • El componente TextInput es un componente de campo de texto similar al elemento <input>.
  • El componente TouchableWithoutFeedback (y otros componentes Touchable*) sirve para capturar diferentes eventos de prensa. Es similar, por ejemplo, al elemento <button>.

Hay algunas diferencias notables entre los componentes principales y los elementos DOM. La primera diferencia es que el componente Text es el único componente React Native que puede tener hijos textuales. Esto significa que no puede, por ejemplo, reemplazar el componente Texto con el Ver

La segunda diferencia notable está relacionada con los controladores de eventos. Mientras trabajamos con los elementos DOM, estamos acostumbrados a agregar controladores de eventos como onClick básicamente a cualquier elemento como <div> y <button>. En React Native tenemos que leer detenidamente la documentación de la API para saber qué controladores de eventos (así como otros accesorios) acepta un componente. Por ejemplo, la familia de componentes "Tocables" proporciona la capacidad de capturar gestos táctiles y puede mostrar comentarios cuando se reconoce un gesto. Uno de estos componentes es el componente TouchableWithoutFeedback, que acepta la propiedad onPress:

import React from 'react';
import { Text, TouchableWithoutFeedback, Alert } from 'react-native';

const TouchableText = props => {
  return (
    <TouchableWithoutFeedback
      onPress={() => Alert.alert('You pressed the text!')}
    >
      <Text>You can press me</Text>
    </TouchableWithoutFeedback>
  );
};

Ahora que tenemos una comprensión básica de los componentes centrales, comencemos a darle a nuestro proyecto algo de estructura. Cree un directorio src en el directorio raíz de su proyecto y en el directorio src cree un directorio components. En el directorio components cree un archivo Main.jsx con el siguiente contenido:

import React from 'react';
import Constants from 'expo-constants';
import { Text, StyleSheet, View } from 'react-native';

const styles = StyleSheet.create({
  container: {
    marginTop: Constants.statusBarHeight,
    flexGrow: 1,
    flexShrink: 1,
  },
});

const Main = () => {
  return (
    <View style={styles.container}>
      <Text>Rate Repository Application</Text>
    </View>
  );
};

export default Main;

A continuación, usemos el componente Main en el componente App en el archivo App.js que se encuentra en el directorio raíz de nuestro proyecto. Reemplace el contenido actual del archivo con esto:

import React from 'react';

import Main from './src/components/Main';

const App = () => {
  return <Main />;
};

export default App;

Recarga manual de la aplicación

Como hemos visto, Expo recargará automáticamente la aplicación cuando hagamos cambios en el código. Sin embargo, puede haber ocasiones en las que la recarga automática no funcione y la aplicación deba recargarse manualmente. Esto se puede lograr a través del menú de desarrollador de la aplicación.

Puede acceder al menú del desarrollador agitando su dispositivo o seleccionando "Shake Gesture" dentro del menú Hardware en el simulador de iOS. También puedes usar el método abreviado de teclado ⌘D cuando tu aplicación se está ejecutando en el simulador de iOS, o ⌘M cuando se ejecuta en un emulador de Android en Mac OS y Ctrl + M en Windows y Linux.

Una vez que el menú del desarrollador esté abierto, simplemente presione "Recargar" para volver a cargar la aplicación. Una vez que se ha recargado la aplicación, las recargas automáticas deberían funcionar sin la necesidad de una recarga manual.

Estilo

Ahora que tenemos una comprensión básica de cómo funcionan los componentes centrales y podemos usarlos para construir una interfaz de usuario simple, es hora de agregar algo de estilo. En la parte 2 aprendimos que en el entorno del navegador podemos definir las propiedades de estilo del componente React usando CSS. Teníamos la opción de definir estos estilos en línea usando el accesorio style o en un archivo CSS con un selector adecuado.

Hay muchas similitudes en la forma en que las propiedades de estilo se adjuntan a los componentes centrales de React Native y en la forma en que se adjuntan a los elementos DOM. En React Native, la mayoría de los componentes principales aceptan un prop llamado style. El prop style acepta un objeto con propiedades de estilo y sus valores. Estas propiedades de estilo son en la mayoría de los casos las mismas que en CSS, sin embargo, los nombres de las propiedades están en camelCase. Esto significa que las propiedades CSS como padding-top y font-size se escriben como paddingTop y fontSize. Aquí hay un ejemplo simple de cómo usar el style prop:

import React from 'react';
import { Text, View } from 'react-native';

const BigBlueText = () => {
  return (
    <View style={{ padding: 20 }}>
      <Text style={{ color: 'blue', fontSize: 24, fontWeight: '700' }}>
        Big blue text
      </Text>
    </View>
  );
};

Además de los nombres de las propiedades, es posible que haya notado otra diferencia en el ejemplo. En CSS, los valores de las propiedades numéricas suelen tener una unidad como px, %, em o rem. En React Native, todos los valores de propiedad relacionados con la dimensión, como width, height, padding y margin, así como la fuente los tamaños son sin unidades. Estos valores numéricos sin unidades representan píxeles independientes de la densidad. En caso de que se esté preguntando cuáles son las propiedades de estilo disponibles para cierto componente principal, consulte la Hoja de trucos de estilo nativo de React.

En general, definir estilos directamente en el objeto style no se considera una gran idea, porque hace que los componentes se vuelvan inflados y poco claros. En cambio, deberíamos definir estilos fuera de la función de renderización del componente usando el método StyleSheet.create. El método StyleSheet.create acepta un único argumento que es un objeto que consta de objetos de estilo con nombre y crea una referencia de estilo StyleSheet a partir del objeto dado. Aquí hay un ejemplo de cómo refactorizar el ejemplo anterior usando el método StyleSheet.create:

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
const styles = StyleSheet.create({  container: {    padding: 20,  },  text: {    color: 'blue',    fontSize: 24,    fontWeight: '700',  },});
const BigBlueText = () => {
  return (
    <View style={styles.container}>      <Text style={styles.text}>        Big blue text
      <Text>
    </View>
  );
};

Creamos dos objetos de estilo con nombre, styles.container y styles.text. Dentro del componente, podemos acceder a un objeto de estilo específico de la misma manera que accederíamos a cualquier clave en un objeto simple.

Además de un objeto, el prop style también acepta una matriz de objetos. En el caso de una matriz, los objetos se fusionan de izquierda a derecha para que las últimas propiedades de estilo tengan prioridad. Esto funciona de forma recursiva, por lo que podemos tener, por ejemplo, una matriz que contenga una matriz de estilos, etc. Si una matriz contiene valores que se evalúan como falsos, como null o undefined, estos valores se ignoran. Esto facilita la definición de estilos condicionales, por ejemplo, basándose en el valor de una propiedad. Aquí hay un ejemplo de estilos condicionales:

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  text: {
    color: 'grey',
    fontSize: 14,
  },
  blueText: {
    color: 'blue',
  },
  bigText: {
    fontSize: 24,
    fontWeight: '700',
  },
});

const FancyText = ({ isBlue, isBig, children }) => {
  const textStyles = [
    styles.text,
    isBlue && styles.blueText,
    isBig && styles.bigText,
  ];

  return <Text style={textStyles}>{children}</Text>;
};

const Main = () => {
  return (
    <>
      <FancyText>Simple text</FancyText>
      <FancyText isBlue>Blue text</FancyText>
      <FancyText isBig>Big text</FancyText>
      <FancyText isBig isBlue>
        Big blue text
      </FancyText>
    </>
  );
};

En el ejemplo usamos el operador && con la instrucción condition && exprIfTrue. Esta declaración produce exprIfTrue si la condition se evalúa como verdadera; de lo contrario, producirá condition, que en ese caso es un valor que se evalúa como falso. Se trata de una abreviatura práctica y muy utilizada. Otra opción sería usar, por ejemplo, el operador condicional, condition ? exprIfTrue : exprIfFalse.

Interfaz de usuario coherente con temas

Sigamos con el concepto de estilo, pero con una perspectiva un poco más amplia. La mayoría de nosotros hemos utilizado una multitud de aplicaciones diferentes y podríamos estar de acuerdo en que una característica que hace que una buena interfaz de usuario sea la coherencia. Esto significa que la apariencia de los componentes de la interfaz de usuario, como el tamaño de fuente, la familia de fuentes y el color, sigue un patrón constante. Para lograr esto, tenemos que parametrizar de alguna manera los valores de diferentes propiedades de estilo. Este método se conoce comúnmente como tematización.

Los usuarios de librerías de interfaces de usuario populares como Bootstrap y Material UI puede que ya esté bastante familiarizado con la temática. Aunque las implementaciones de temas son diferentes, la idea principal es siempre usar variables como colors.primary en lugar de "números mágicos" como #0366d6 al definir estilos. Esto conduce a una mayor consistencia y flexibilidad.

Veamos cómo la tematización podría funcionar en la práctica en nuestra aplicación. Usaremos mucho texto con diferentes variaciones, como diferentes tamaños de fuente y colores. Debido a que React Native no admite estilos globales, deberíamos crear nuestro propio componente Text para mantener la coherencia del contenido textual. Comencemos agregando el siguiente objeto de configuración de tema en un archivo theme.js en el directorio src:

const theme = {
  colors: {
    textPrimary: '#24292e',
    textSecondary: '#586069',
    primary: '#0366d6',
  },
  fontSizes: {
    body: 14,
    subheading: 16,
  },
  fonts: {
    main: 'System',
  },
  fontWeights: {
    normal: '400',
    bold: '700',
  },
};

export default theme;

A continuación, deberíamos crear el componente actual Text que utiliza esta configuración de tema. Cree un archivo Text.jsx en el directorio components donde ya tenemos nuestros otros componentes. Agregue el siguiente contenido al archivo Text.jsx:

import React from 'react';
import { Text as NativeText, StyleSheet } from 'react-native';

import theme from '../theme';

const styles = StyleSheet.create({
  text: {
    color: theme.colors.textPrimary,
    fontSize: theme.fontSizes.body,
    fontFamily: theme.fonts.main,
    fontWeight: theme.fontWeights.normal,
  },
  colorTextSecondary: {
    color: theme.colors.textSecondary,
  },
  colorPrimary: {
    color: theme.colors.primary,
  },
  fontSizeSubheading: {
    fontSize: theme.fontSizes.subheading,
  },
  fontWeightBold: {
    fontWeight: theme.fontWeights.bold,
  },
});

const Text = ({ color, fontSize, fontWeight, style, ...props }) => {
  const textStyle = [
    styles.text,
    color === 'textSecondary' && styles.colorTextSecondary,
    color === 'primary' && styles.colorPrimary,
    fontSize === 'subheading' && styles.fontSizeSubheading,
    fontWeight === 'bold' && styles.fontWeightBold,
    style,
  ];

  return <NativeText style={textStyle} {...props} />;
};

export default Text;

Ahora hemos implementado nuestro propio componente de texto con variantes de color, tamaño de fuente y peso de fuente consistentes que podemos usar en cualquier lugar de nuestra aplicación. Podemos obtener diferentes variaciones de texto usando diferentes props como este:

import React from 'react';

import Text from './Text';

const Main = () => {
  return (
    <>
      <Text>Simple text</Text>
      <Text style={{ paddingBottom: 10 }}>Text with custom style</Text>
      <Text fontWeight="bold" fontSize="subheading">
        Bold subheading
      </Text>
      <Text color="textSecondary">Text with secondary color</Text>
    </>
  );
};

export default Main;

No dude en ampliar o modificar este componente si lo desea. También puede ser una buena idea crear componentes de texto reutilizables como Subheading que utilizan el componente Text. Además, siga ampliando y modificando la configuración del tema a medida que avanza su aplicación.

Usando flexbox para el diseño

El último concepto que cubriremos relacionado con el estilo es implementar diseños con flexbox. Aquellos que están más familiarizados con CSS saben que flexbox no está relacionado solo con React Native, también tiene muchos casos de uso en el desarrollo web. De hecho, aquellos que saben cómo funciona flexbox en el desarrollo web probablemente no aprenderán mucho de esta sección. Sin embargo, aprendamos o revisemos los conceptos básicos de flexbox.

Flexbox es una entidad de diseño que consta de dos componentes separados: un contenedor flexible y dentro de él un conjunto de elementos flexibles. El contenedor flexible tiene un conjunto de propiedades que controlan el flujo de sus artículos. Para convertir un componente en un contenedor flexible, debe tener la propiedad de estilo display establecida como flex, que es el valor predeterminado para la propiedad display. Aquí hay un ejemplo de un contenedor flexible:

import React from 'react';
import { View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  flexContainer: {
    flexDirection: 'row',
  },
});

const FlexboxExample = () => {
  return <View style={styles.flexContainer}>{/* ... */}</View>;
};

Quizás las propiedades más importantes de un contenedor flexible son las siguientes:

  • la propiedad flexDirection controla la dirección en la que los artículos flex se colocan dentro del contenedor. Los valores posibles para esta propiedad son row, row-reverse, column (valor predeterminado) y reverse-column. La dirección de flex row colocará los elementos flexibles de izquierda a derecha, mientras que la column de arriba a abajo. Las direcciones *-reverse simplemente invertirán el orden de los elementos flexibles.
  • La propiedad justifyContent controla la alineación de los elementos flexibles a lo largo del eje principal (definido por la propiedad flexDirection). Los valores posibles para esta propiedad son flex-start (valor predeterminado), flex-end, center, space-between, space-around y space-evenly.
  • La propiedad alignItems hace lo mismo que justifyContent pero para el eje opuesto. Los valores posibles para esta propiedad son flex-start, flex-end, center, baseline y stretch (valor predeterminado).

Pasemos a los elementos flexibles. Como se mencionó, un contenedor flexible puede contener uno o varios elementos flexibles. Los elementos flexibles tienen propiedades que controlan cómo se comportan con respecto a otros elementos flexibles en el mismo contenedor flexible. Para convertir un componente en un elemento flexible, todo lo que tiene que hacer es configurarlo como hijo inmediato de un contenedor flexible:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  flexContainer: {
    display: 'flex',
  },
  flexItemA: {
    flexGrow: 0,
    backgroundColor: 'green',
  },
  flexItemB: {
    flexGrow: 1,
    backgroundColor: 'blue',
  },
});

const FlexboxExample = () => {
  return (
    <View style={styles.flexContainer}>
      <View style={styles.flexItemA}>
        <Text>Flex item A</Text>
      </View>
      <View style={styles.flexItemB}>
        <Text>Flex item B</Text>
      </View>
    </View>
  );
};

Una de las propiedades más utilizadas del elemento flexible es la propiedad flexGrow. Acepta un valor sin unidades que define la capacidad de un artículo flexible para crecer si es necesario. Si todos los elementos flexibles tienen un flexGrow de 1, compartirán todo el espacio disponible de manera uniforme. Si un elemento flexible tiene un flexGrow de 0, solo usará el espacio que requiere su contenido y dejará el resto del espacio para otros elementos flexibles.

Aquí hay un ejemplo más interactivo y concreto de cómo usar flexbox para implementar un componente de tarjeta simple con encabezado, cuerpo y pie de página: ejemplo de Flexbox.

A continuación, lea el artículo Una guía completa de Flexbox que tiene ejemplos visuales completos de flexbox. También es una buena idea jugar con las propiedades de flexbox en Flexbox Playground para ver cómo diferentes propiedades de flexbox afectan el diseño. Recuerde que en React Native los nombres de las propiedades son los mismos que los de CSS excepto por el nombre en camelCase. Sin embargo, los valores de propiedad como flex-start y space-between son exactamente iguales.

NB: React Native y CSS tienen algunas diferencias con respecto al flexbox. La diferencia más importante es que en React Native el valor predeterminado para la propiedad flexDirection es column. También vale la pena señalar que la abreviatura flex no acepta múltiples valores en React Native. Se puede leer más sobre la implementación de Flexbox de React Native en la documentación.

Enrutamiento

Cuando comencemos a expandir nuestra aplicación, necesitaremos una forma de transición entre diferentes vistas, como la vista de repositorios y la vista de inicio de sesión. En la parte 7 nos familiarizamos con la biblioteca React router y aprendimos cómo usarla para implementar el enrutamiento en una aplicación web.

El enrutamiento en una aplicación React Native es un poco diferente al enrutamiento en una aplicación web. La principal diferencia es que no podemos hacer referencia a páginas con URL, que escribimos en la barra de direcciones del navegador, y no podemos navegar hacia adelante y hacia atrás a través del historial del usuario usando los navegadores API de historial. Sin embargo, esto es solo cuestión de la interfaz del enrutador que estamos usando.

Con React Native podemos usar todo el núcleo del enrutador React, incluidos los ganchos y componentes. La única diferencia con el entorno del navegador es que debemos reemplazar el BrowserRouter con NativeRouter compatible con React Native , proporcionado por la biblioteca react-router-native. Comencemos instalando la librería react-router-native:

npm install react-router-native

El uso de la librería react-router-native romperá la vista previa del navegador web de Expo. Sin embargo, otras vistas previas funcionarán igual que antes. Podemos solucionar el problema ampliando la configuración del Webpack de la Expo para que transpile las fuentes de la biblioteca react-router-native con Babel. Para extender la configuración de Webpack, necesitamos instalar la librería @expo/webpack-config:

npm install @expo/webpack-config --save-dev

A continuación, cree un archivo webpack.config.js en el directorio raíz de su proyecto con el siguiente contenido:

const path = require('path');
const createExpoWebpackConfigAsync = require('@expo/webpack-config');

module.exports = async function(env, argv) {
  const config = await createExpoWebpackConfigAsync(env, argv);

  config.module.rules.push({
    test: /\.js$/,
    loader: 'babel-loader',
    include: [path.join(__dirname, 'node_modules/react-router-native')],
  });

  return config;
};

Finalmente, reinicie las herramientas de desarrollo de Expo para que se aplique nuestra nueva configuración de Webpack.

Ahora que la vista previa del navegador web de la Expo está arreglada, abra el archivo App.js y agregue el componente NativeRouter al componente App:

import React from 'react';
import { NativeRouter } from 'react-router-native';
import Main from './src/components/Main';

const App = () => {
  return (
    <NativeRouter>      <Main />
    </NativeRouter>  );
};

export default App;

Una vez que el enrutador esté en su lugar, agreguemos nuestra primera ruta al componente Main en el archivo Main.jsx:

import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Route, Switch, Redirect } from 'react-router-native';
import RepositoryList from './RepositoryList';
import AppBar from './AppBar';
import theme from '../theme';

const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.mainBackground,
    flexGrow: 1,
    flexShrink: 1,
  },
});

const Main = () => {
  return (
    <View style={styles.container}>
      <AppBar />
      <Switch>        <Route path="/" exact>          <RepositoryList />        </Route>        <Redirect to="/" />      </Switch>    </View>
  );
};

export default Main;

Gestión del estado del formulario

Ahora que tenemos un marcador de posición para la vista de inicio de sesión, el siguiente paso sería implementar el formulario de inicio de sesión. Antes de llegar a eso, hablemos de las formas en una perspectiva más amplia.

La implementación de formularios depende en gran medida de la gestión del estado. Usar el hook useState de React para la administración del estado podría hacer el trabajo para formularios más pequeños. Sin embargo, rápidamente hará que la gestión de estado sea bastante tediosa con formas más complejas. Afortunadamente, hay muchas bibliotecas buenas en el ecosistema React que facilitan la gestión de estado de formularios. Una de estas bibliotecas es Formik.

Los conceptos principales de Formik son el contexto y un campo. El contexto de Formik lo proporciona el componente Formik que contiene el estado del formulario. El estado consta de información de los campos del formulario. Esta información incluye, por ejemplo, el valor y los errores de validación de cada campo. Se puede hacer referencia a los campos del estado por su nombre utilizando el hook useField o el componente Field.

Veamos cómo funciona esto realmente creando un formulario para calcular el índice de masa corporal:

import React from 'react';
import { Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
import { Formik, useField } from 'formik';

const initialValues = {
  mass: '',
  height: '',
};

const getBodyMassIndex = (mass, height) => {
  return Math.round(mass / Math.pow(height, 2));
};

const BodyMassIndexForm = ({ onSubmit }) => {
  const [massField, massMeta, massHelpers] = useField('mass');
  const [heightField, heightMeta, heightHelpers] = useField('height');

  return (
    <View>
      <TextInput
        placeholder="Weight (kg)"
        value={massField.value}
        onChangeText={text => massHelpers.setValue(text)}
      />
      <TextInput
        placeholder="Height (m)"
        value={heightField.value}
        onChangeText={text => heightHelpers.setValue(text)}
      />
      <TouchableWithoutFeedback onPress={onSubmit}>
        <Text>Calculate</Text>
      </TouchableWithoutFeedback>
    </View>
  );
};

const BodyMassIndexCalculator = () => {
  const onSubmit = values => {
    const mass = parseFloat(values.mass);
    const height = parseFloat(values.height);

    if (!isNaN(mass) && !isNaN(height) && height !== 0) {
      console.log(`Your body mass index is: ${getBodyMassIndex(mass, height)}`);
    }
  };

  return (
    <Formik initialValues={initialValues} onSubmit={onSubmit}>
      {({ handleSubmit }) => <BodyMassIndexForm onSubmit={handleSubmit} />}
    </Formik>
  );
};

Este ejemplo no es parte de nuestra aplicación, por lo que no es necesario que agregue este código a la aplicación. Sin embargo, puede probarlo, por ejemplo, en Expo Snack. Expo Snack es un editor en línea para React Native, similar a JSFiddle y CodePen. Es una plataforma útil para probar código rápidamente. Puede compartir Expo Snacks con otros usando un enlace o incrustándolos como un Snack Player en un sitio web. Es posible que se haya topado con Snack Players, por ejemplo, en este material y en la documentación de React Native.

En el ejemplo, definimos el contexto Formik en el componente BodyMassIndexCalculator y le proporcionamos valores iniciales y una devolución de llamada de envío. Los valores iniciales se proporcionan a través de la propiedad initialValues como un objeto con nombres de campo como claves y los valores iniciales correspondientes como valores. La devolución de llamada de envío se proporciona a través del prop onSubmit y se llama cuando el la función handleSubmit es llamada, con la condición de que no haya errores de validación. Los hijos del componente Formik son una función que se llama con props incluyendo información relacionada con el estado y acciones como la función handleSubmit.

El componente BodyMassIndexForm contiene los enlaces de estado entre el contexto y las entradas de texto. Usamos el hook useField para obtener el valor de un campo y cambiarlo. El hook useField tiene un argumento que es el nombre del campo y devuelve una matriz con tres valores, [field, meta, helpers]. El objeto de campo contiene el valor del campo, el metaobjeto contiene metainformación del campo, como un posible mensaje de error y el objeto de ayuda contiene diferentes acciones para cambiar el estado del campo, como la función setValue. Tenga en cuenta que el componente que usa el hook useField tiene que estar dentro del contexto de Formik. Esto significa que el componente debe ser descendiente del componente Formik.

Aquí hay una versión interactiva de nuestro ejemplo anterior: Ejemplo de Formik.

En el ejemplo anterior, el uso del hook useField con el componente TextInput provoca un código repetitivo. Extraigamos este código repetitivo en un componente FormikTextInput y creemos un componente TextInput personalizado para hacer que las entradas de texto sean un poco más agradables visualmente. Primero, instalemos Formik:

npm install formik

A continuación, cree un archivo TextInput.jsx en el directorio components con el siguiente contenido:

import React from 'react';
import { TextInput as NativeTextInput, StyleSheet } from 'react-native';

const styles = StyleSheet.create({});

const TextInput = ({ style, error, ...props }) => {
  const textInputStyle = [style];

  return <NativeTextInput style={textInputStyle} {...props} />;
};

export default TextInput;

Pasemos al componente FormikTextInput que agrega los enlaces de estado de Formik al componente TextInput. Cree un archivo FormikTextInput.jsx en el directorio components con el siguiente contenido:

import React from 'react';
import { StyleSheet } from 'react-native';
import { useField } from 'formik';

import TextInput from './TextInput';
import Text from './Text';

const styles = StyleSheet.create({
  errorText: {
    marginTop: 5,
  },
});

const FormikTextInput = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  const showError = meta.touched && meta.error;

  return (
    <>
      <TextInput
        onChangeText={value => helpers.setValue(value)}
        onBlur={() => helpers.setTouched(true)}
        value={field.value}
        error={showError}
        {...props}
      />
      {showError && <Text style={styles.errorText}>{meta.error}</Text>}
    </>
  );
};

export default FormikTextInput;

Al usar el componente FormikTextInput podríamos refactorizar el componente BodyMassIndexForm en el ejemplo anterior de esta manera:

const BodyMassIndexForm = ({ onSubmit }) => {
  return (
    <View>
      <FormikTextInput name="mass" placeholder="Weight (kg)" />      <FormikTextInput name="height" placeholder="Height (m)" />      <TouchableWithoutFeedback onPress={onSubmit}>
        <Text>Calculate</Text>
      </TouchableWithoutFeedback>
    </View>
  );
};

Como podemos ver, la implementación del componente FormikTextInput que maneja los enlaces de Formik del componente TextInput ahorra mucho código. Si sus formularios de Formik utilizan otros componentes de entrada, es una buena idea implementar abstracciones similares para ellos también.

Validación del formulario

Formik ofrece dos enfoques para la validación de formularios: una función de validación o un esquema de validación. Una función de validación es una función proporcionada para el componente Formik como el valor del prop validate. Recibe los valores del formulario como argumento y devuelve un objeto que contiene posibles mensajes de error específicos del campo.

El segundo enfoque es el esquema de validación que se proporciona para el componente Formik como el valor del prop validationSchema. Este esquema de validación se puede crear con una librería de validación llamada Yup. Comencemos instalando Yup:

npm install yup

A continuación, como ejemplo, creemos un esquema de validación para el formulario de índice de masa corporal que implementamos anteriormente. Queremos validar que los campos mass y height estén presentes y sean numéricos. Además, el valor de mass debe ser mayor o igual a 1 y el valor de height debe ser mayor o igual a 0.5. Así es como definimos el esquema:

import React from 'react';
import * as yup from 'yup';
// ...

const validationSchema = yup.object().shape({  mass: yup    .number()    .min(1, 'Weight must be greater or equal to 1')    .required('Weight is required'),  height: yup    .number()    .min(0.5, 'Height must be greater or equal to 0.5')    .required('Height is required'),});
const BodyMassIndexCalculator = () => {
  // ...

  return (
    <Formik
      initialValues={initialValues}
      onSubmit={onSubmit}
      validationSchema={validationSchema}    >
      {({ handleSubmit }) => <BodyMassIndexForm onSubmit={handleSubmit} />}
    </Formik>
  );
};

La validación se realiza de forma predeterminada cada vez que cambia el valor de un campo y cuando se llama a la función handleSubmit. Si la validación falla, no se llama a la función provista para la propiedad onSubmit del componente Formik.

El componente FormikTextInput que implementamos anteriormente muestra el mensaje de error del campo si está presente y el campo está "tocado", lo que significa que el campo ha recibido y perdido el foco:

const FormikTextInput = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);

  // Check if the field is touched and the error message is present
  const showError = meta.touched && meta.error;

  return (
    <>
      <TextInput
        onChangeText={(value) => helpers.setValue(value)}
        onBlur={() => helpers.setTouched(true)}
        value={field.value}
        error={showError}
        {...props}
      />
      {/* Show the error message if the value of showError variable is true  */}
      {showError && <Text style={styles.errorText}>{meta.error}</Text>}
    </>
  );
};

Código específico de la plataforma

Una gran ventaja de React Native es que no tenemos que preocuparnos por si la aplicación se ejecuta en un dispositivo Android o iOS. Sin embargo, puede haber casos en los que necesitemos ejecutar código específico de la plataforma. Tal caso podría ser, por ejemplo, el uso de una implementación diferente de un componente en una plataforma diferente.

Podemos acceder a la plataforma del usuario a través de la constante Platform.OS:

import { React } from 'react';
import { Platform, Text, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  text: {
    color: Platform.OS === 'android' ? 'green' : 'blue',
  },
});

const WhatIsMyPlatform = () => {
  return <Text style={styles.text}>Your platform is: {Platform.OS}</Text>;
};

Los valores posibles para la constante Platform.OS son android y ios. Otra forma útil de definir ramas de código específicas de la plataforma es utilizar el método Platform.select. Dado un objeto cuyas claves son ios, android, native y default, El método Platform.select devuelve el valor más adecuado para la plataforma en la que se está ejecutando el usuario. Podemos reescribir la variable styles en el ejemplo anterior usando el método Platform.select como este:

const styles = StyleSheet.create({
  text: {
    color: Platform.select({
      android: 'green',
      ios: 'blue',
      default: 'black',
    }),
  },
});

Incluso podemos usar el método Platform.select para requerir un componente específico de la plataforma:

const MyComponent = Platform.select({
  ios: () => require('./MyIOSComponent'),
  android: () => require('./MyAndroidComponent'),
})();

<MyComponent />;

Sin embargo, un método más sofisticado para implementar e importar componentes específicos de la plataforma (o cualquier otro fragmento de código) es utilizar las extensiones de archivo .io.jsx y .android.jsx. Tenga en cuenta que la extensión .jsx también puede ser cualquier extensión reconocida por el paquete, como .js. Por ejemplo, podemos tener archivos Button.ios.jsx que podemos importar así:

import React from 'react';

import Button from './Button';

const PlatformSpecificButton = () => {
  return <Button />;
};

Ahora, el paquete de Android de la aplicación tendrá el componente definido en Button.android.jsx mientras que el paquete de iOS tendrá uno definido en el archivo Button.ios.jsx.