react native

John Cardozo

John Cardozo

react native

qué es react native?

React Native es un framework open-source creado por Meta enfocado al desarrollo de aplicaciones para Android, Android TV, iOS, macOS, tvOS, Web, Windows y UWP.

Pros

Cons

Eficiencia de costos

Optimización de recursos: desarrolladores con las mismas habilidades

Código base unificado: mismo código para todas las plataformas

Desempeño ligeramente menor que aplicaciones nativas

Problemas en actualizaciones de librerías externas

Brecha con nuevas características de los sistemas operativos

uso de react native

Showcase

Uso de plataformas móviles

desarrollo nativo vs desarrollo hibrido

Nativo

Híbrido

iOS

Android

Swift

Java

Kotlin

Javascript

Código base

Múltiples repos

Equipo de desarrollo / Testing

Cambios de reglas de negocio 

React Native

iOS

Android

Desempeño

Uso de utilidades del dispositivo

Librerías de terceros

herramientas y pre-requisitos

Expo

VSCode

HTML

CSS

Javascript

React

NodeJS

Git

Android Studio

XCode

android studio & ios Simulator

iOS

open -a Simulator.app

Se puede ejecutar la app Simulator.app desde la Terminal o desde un launcher

Android

Se descarga Android Studio y se crea un device

inicia el emulador

Crea un nuevo dispositivo

~/Library/Android/sdk/emulator/emulator -list-avds
~/Library/Android/sdk/emulator/emulator -avd Pixel_6_Pro_API_33

lista de emuladores

inicia el emulador

creación del proyecto

Creación del proyecto

npx create-expo-app --template
sudo npm install -g create-expo-app

Opcional: Actualización de create-expo-app

- cd my-coffee
- npm run android
- npm run ios
- npm run web

Siguientes pasos

Computador Mac

Ejecución web: Si muestra error, hay que instalar paquetes adicionales

npx expo install react-native-web@~0.18.9 react-dom@18.1.0 @expo/webpack-config@^0.17.2

Es posible que solo funcione con Node 16

ERR_OSSL_EVP_UNSUPPORTED

Simulador en ejecución

Cualquier Sistema Operativo

Emulador en ejecución

npm start

blank

archivo inicial

App.js

import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Open up App.js to start working on your app!</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

importa componentes

componente principal

Estilos del componente

Documentación de componentes

ejemplo de trabajo

reorganización inicial de componentes

App.js

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

export default function App() {
  return <Main />;
}

components / Main.jsx

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

const Main = () => {
  return (
    <View>
      <Text>Our coffee</Text>
    </View>
  );
};

export default Main;

Establecer el área de la aplicación

components / Main.jsx

import React from "react";
import { SafeAreaView, StyleSheet, StatusBar, Text } from "react-native";

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <Text>Our Coffee</Text>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
  },
});

export default Main;

SafeAreaView establece el área donde la aplicación se puede visualizar

mostrar elementos de una lista

data / coffees.js

export const coffees = [
  {
    id: "1", product: "Mocca", brand: "Juan Valdez", 
    country: "Colombia", body: "full", acidity: "milt", 
    notes: "caramel", stars: 4,
  },
  {
    id: "2", product: "Vesuvio", brand: "Kolb", 
    country: "Kenia", body: "smooth", acidity: "low", 
    notes: "chocolate", stars: 3,
  },
  {
    id: "3", product: "RR", brand: "Kolb", 
    country: "Camerun", body: "full", acidity: "milt", 
    notes: "hazelnut", stars: 4,
  },
];

components / CoffeeList.jsx

import React from "react";
import { View, Text } from "react-native";
import { coffees } from "../data/coffees";

const CoffeeList = () => {
  return (
    <View>
      {coffees.map((coffee) => (
        <View key={coffee.id}>
          <Text>{coffee.product}</Text>
        </View>
      ))}
    </View>
  );
};
export default CoffeeList;

Map sobre la lista para generar los nuevos componentes

El key es requerido

Main.jsx

import CoffeeList from "./CoffeeList";

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <CoffeeList />
    </SafeAreaView>
  );
};

mostrar elementos de una lista con scroll

import React from "react";
import { View, Text } from "react-native";
import { coffees } from "../data/coffees";

const CoffeeList = () => {
  return (
    <View>
      {coffees.map((coffee) => (
        <View key={coffee.id}>
          <Text>{coffee.id}</Text>
          <Text>{coffee.product}</Text>
          <Text>{coffee.brand}</Text>
          <Text>{coffee.country}</Text>
          <Text>{coffee.body}</Text>
          <Text>{coffee.acidity}</Text>
          <Text>{coffee.notes}</Text>
          <Text>{coffee.stars}</Text>
        </View>
      ))}
    </View>
  );
};

Sin scroll

components / CoffeeList.jsx

import React from "react";
import { View, FlatList, Text } from "react-native";
import { coffees } from "../data/coffees";

const CoffeeList = () => {
  return (
    <FlatList
      data={coffees}
      ItemSeparatorComponent={<Text> </Text>}
      renderItem={({ item }) => (
        <View>
          <Text>{item.id}</Text>
          <Text>{item.product}</Text>
          <Text>{item.brand}</Text>
          <Text>{item.country}</Text>
          <Text>{item.body}</Text>
          <Text>{item.acidity}</Text>
          <Text>{item.notes}</Text>
          <Text>{item.stars}</Text>
        </View>
      )}
      keyExtractor={(item) => item.id}
    />
  );
};

Con scroll

map

FlatList

creación de un componente ítem

import React from "react";
import { View, FlatList, Text } from "react-native";
import { coffees } from "../data/coffees";
import CoffeeItem from "./CoffeeItem";

const CoffeeList = () => {
  return (
    <FlatList
      data={coffees}
      ItemSeparatorComponent={<Text> </Text>}
      renderItem={({ item }) => <CoffeeItem item={item} />}
      keyExtractor={(item) => item.id}
    />
  );
};

export default CoffeeList;

components / CoffeeList.jsx

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

const CoffeeItem = ({ item }) => {
  return (
    <View>
      <Text>{item.id}</Text>
      <Text>{item.product}</Text>
      <Text>{item.brand}</Text>
      <Text>{item.country}</Text>
      <Text>{item.body}</Text>
      <Text>{item.acidity}</Text>
      <Text>{item.notes}</Text>
      <Text>{item.stars}</Text>
    </View>
  );
};

export default CoffeeItem;

components / CoffeeItem.jsx

estilos de componentes

estilos de componentes

creación de estilos - inline

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

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={{ padding: 20 }}>
      <Text>{coffee.id}</Text>
      <Text style={{ fontWeight: 500 }}>{coffee.product}</Text>
      <Text>{coffee.brand}</Text>
      <Text>{coffee.country}</Text>
      <Text>{coffee.body}</Text>
      <Text>{coffee.acidity}</Text>
      <Text>{coffee.notes}</Text>
      <Text>{coffee.stars}</Text>
    </View>
  );
};

export default CoffeeItem;

components / CoffeeItem.jsx

Estilos mezclados con JSX

No es la mejor práctica

creación de estilos - stylesheets

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

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <Text>{coffee.id}</Text>
      <Text style={styles.product}>{coffee.product}</Text>
      <Text>{coffee.brand}</Text>
      <Text>{coffee.country}</Text>
      <Text>{coffee.body}</Text>
      <Text>{coffee.acidity}</Text>
      <Text>{coffee.notes}</Text>
      <Text>{coffee.stars}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  product: {
    fontWeight: "500",
    color: "brown",
  },
});

export default CoffeeItem;

components / CoffeeItem.jsx

Definición de estilos

Uso de estilos

creación de estilos - componente styled

import { StyleSheet, Text } from "react-native";

const styles = StyleSheet.create({
  text: {
    fontSize: 15,
    color: "gray",
  },
  bold: {
    fontWeight: "bold",
  },
  blue: {
    color: "blue",
  },
  big: {
    fontSize: 20,
  },
  small: {
    fontSize: 12,
  },
});

export default function StyledText({ blue, bold, children, big, small }) {
  const textStyles = [
    styles.text,
    blue && styles.blue,
    bold && styles.bold,
    big && styles.big,
    small && styles.small,
  ];
  return <Text style={textStyles}>{children}</Text>;
}

components / StyledText.jsx

import React from "react";
import { View, Text, StyleSheet } from "react-native";
import StyledText from "./StyledText";

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <StyledText big bold>{coffee.id}</StyledText>
      <StyledText bold>{coffee.product}</StyledText>
      <StyledText blue>{coffee.brand}</StyledText>
      <StyledText small>{coffee.country}</StyledText>
      <StyledText small>{coffee.body}</StyledText>
      <StyledText small>{coffee.acidity}</StyledText>
      <StyledText small>{coffee.notes}</StyledText>
      <StyledText small>{coffee.stars}</StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  }
});

export default CoffeeItem;

components / CoffeeItem.jsx

Uso del componente

Definición del componente

creación de temas

const theme = {
  colors: {
    textPrimary: "#555555", 
    textSecondary: "#9d9d9d",
    background: "#d7d7d8",
    cardBackground: "#fff",
  },
  fontSizes: {
    heading: 20,
    subheading: 17,
    normal: 16,
  },
  fonts: {
    main: "System",
  },
  fontWeights: {
    normal: "400",
    bold: "700",
  },
};

export default theme;

theme.js

import { StyleSheet, Text } from "react-native";
import theme from "../theme.js";

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

export default function StyledText({
  children,
  color,
  fontSize,
  fontWeight,
  ...props
}) {
  const textStyles = [
    styles.text,
    color === "primary" && styles.colorPrimary,
    color === "secondary" && styles.colorSecondary,
    fontSize === "subheading" && styles.subheading,
    fontSize === "heading" && styles.heading,
    fontWeight === "bold" && styles.bold,
  ];
  return (
    <Text style={textStyles} {...props}>
      {children}
    </Text>
  );
}

components / StyledText.jsx

import React from "react";
import { View, Text, StyleSheet } from "react-native";
import StyledText from "./StyledText";

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <StyledText fontWeight="bold" fontSize="heading">
        {coffee.product}
      </StyledText>
      <StyledText fontSize="subheading" fontWeight="bold">
        {coffee.brand}
      </StyledText>
      <StyledText>{coffee.country}</StyledText>
      <StyledText>{coffee.body}</StyledText>
      <StyledText>{coffee.acidity}</StyledText>
      <StyledText>{coffee.notes}</StyledText>
      <StyledText>{coffee.stars}</StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  product: {
    fontWeight: "500",
    color: "brown",
  },
});

export default CoffeeItem;

components / CoffeeItem.jsx

flexbox

import { StyleSheet, View } from "react-native";
import StyledText from "./StyledText";

const CoffeeItemHeader = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <StyledText fontWeight="bold" fontSize="heading">
        {coffee.product}
      </StyledText>
      <StyledText>{coffee.stars}</StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
  },
});

export default CoffeeItemHeader;

components / CoffeeItemHeader.jsx

import React from "react";
import { View, StyleSheet } from "react-native";
import StyledText from "./StyledText";
import CoffeeItemHeader from "./CoffeeItemHeader";

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <CoffeeItemHeader coffee={coffee} />
      <StyledText fontSize="subheading" fontWeight="bold">
        {coffee.brand}, {coffee.country}
      </StyledText>
      <StyledText>
        {coffee.body}, {coffee.acidity}, {coffee.notes}
      </StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
});

export default CoffeeItem;

components / CoffeeItem.jsx

Reglas Flexbox

Estilos generales

import React from "react";
import { FlatList, StyleSheet } from "react-native";
import { coffees } from "../data/coffees";
import CoffeeItem from "./CoffeeItem";

import theme from "../theme.js";

const CoffeeList = () => {
  return (
    <FlatList
      style={styles.container}
      data={coffees}
      renderItem={({ item }) => <CoffeeItem coffee={item} />}
      keyExtractor={(item) => item.id}
    />
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.background,
  },
});

export default CoffeeList;

components / CoffeeList.jsx

import React from "react";
import { View, StyleSheet } from "react-native";
import StyledText from "./StyledText";
import CoffeeItemHeader from "./CoffeeItemHeader";
import theme from "../theme.js";

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <CoffeeItemHeader coffee={coffee} />
      <StyledText fontSize="subheading" fontWeight="bold">
        {coffee.brand}, {coffee.country}
      </StyledText>
      <StyledText>
        {coffee.body}, {coffee.acidity}, {coffee.notes}
      </StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: theme.colors.cardBackground,
    marginTop: 6,
    marginBottom: 6,
    marginLeft: 15,
    marginRight: 15,
    borderRadius: 15,
  },
});

export default CoffeeItem;

components / CoffeeItem.jsx

Estilos CSS

imágenes

export const coffees = [
  {
    id: "1",
    product: "Mocca",
    brand: "Juan Valdez",
    country: "Colombia",
    body: "full",
    acidity: "milt",
    notes: "caramel",
    stars: 4,
    img: "https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=687&q=80",
  },
  {
    id: "2",
    product: "Vesuvio",
    brand: "Kolb",
    country: "Kenia",
    body: "smooth",
    acidity: "low",
    notes: "chocolate",
    stars: 3,
    img: "https://images.unsplash.com/photo-1595434091143-b375ced5fe5c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=735&q=80",
  },
  ...
];

data / coffees.js

import React from "react";
import { Image, View, StyleSheet } from "react-native";
import StyledText from "./StyledText";
import CoffeeItemHeader from "./CoffeeItemHeader";
import theme from "../theme.js";

const CoffeeItem = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <Image style={styles.image} source={{ uri: coffee.img }} />
      <CoffeeItemHeader coffee={coffee} />
      <StyledText fontSize="subheading" fontWeight="bold">
        {coffee.brand}, {coffee.country}
      </StyledText>
      <StyledText>
        {coffee.body}, {coffee.acidity}, {coffee.notes}
      </StyledText>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: theme.colors.cardBackground,
    marginTop: 6,
    marginBottom: 6,
    marginLeft: 15,
    marginRight: 15,
    borderRadius: 15,
  },
  image: {
    height: 50,
    borderRadius: 4,
    marginBottom: 5,
  },
});

export default CoffeeItem;

components / CoffeeItem.jsx

Estilo y source de imágenes

app bar

const { StyleSheet, View, StatusBar, Text } = require("react-native");
import theme from "../theme.js";

const AppBar = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Our Coffee</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.appBarBackground,
    paddingTop: StatusBar.currentHeight + 10,
    paddingBottom: 10,
    paddingLeft: 15,
  },
  text: {
    color: theme.colors.cardBackground,
    fontSize: theme.fontSizes.heading,
    fontWeight: theme.fontWeights.bold,
  },
});

export default AppBar;

components / AppBar.jsx

import React from "react";

import { SafeAreaView, StyleSheet, StatusBar } from "react-native";
import CoffeeList from "./CoffeeList";
import AppBar from "./AppBar";

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <AppBar />
      <CoffeeList />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    //marginTop: StatusBar.currentHeight || 0,
  },
});

export default Main;

components / Main.jsx

Ya no es necesario: se elimina

const theme = {
  colors: {
    textPrimary: "#555555",
    textSecondary: "#9d9d9d",
    background: "#d7d7d8",
    cardBackground: "#fff",
    appBarBackground: "#B79383",
  },
};

theme.js

stars component

import { Text, View } from "react-native";

const CoffeeStars = ({ stars }) => {
  const array = Array(stars).fill(0);
  return (
    <View style={{ flexDirection: "row" }}>
      {array.map((star, index) => (
        <Text key={index}>⭐️</Text>
      ))}
    </View>
  );
};

export default CoffeeStars;

components / CoffeeStars.jsx

import { StyleSheet, View } from "react-native";
import StyledText from "./StyledText";
import CoffeeStars from "./CoffeeStars";

const CoffeeItemHeader = ({ coffee }) => {
  return (
    <View style={styles.container}>
      <StyledText fontWeight="bold" fontSize="heading">
        {coffee.product}
      </StyledText>
      <CoffeeStars stars={coffee.stars} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
  },
});

export default CoffeeItemHeader;

components / CoffeeHeader.jsx

Inicialización de arreglo

rutas

rutas

react routing native

npm install react-router-native

Instalación del paquete NodeJS

import Main from "./src/components/Main";
import { NativeRouter } from "react-router-native";
import { StatusBar } from "expo-status-bar";

export default function App() {
  return (
    <>
      <StatusBar style="light" />
      <NativeRouter>
        <Main />
      </NativeRouter>
    </>
  );
}

App.js

Se envuelve la aplicación con NativeRouter

StatusBar: light | dark

react routing native

import React from "react";
import { Text, SafeAreaView, StyleSheet } from "react-native";
import { Route, Routes } from "react-router-native";

import CoffeeList from "./CoffeeList";
import AppBar from "./AppBar";

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <AppBar />
      <Routes>
        <Route path="/" element={<CoffeeList />} />
        <Route path="/signin" element={<Text>SignIn</Text>} />
      </Routes>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

export default Main;

components / Main.jsx

Routes + Route

const { StyleSheet, View, StatusBar, Text } = require("react-native");
import { Link } from "react-router-native";
import theme from "../theme.js";
import StyledText from "./StyledText.jsx";

const AppBarTab = ({ children, to }) => {
  return (
    <Link to={to}>
      <StyledText fontWeight="bold" style={styles.text}>
        {children}
      </StyledText>
    </Link>
  );
};
const AppBar = () => {
  return (
    <View style={styles.container}>
      <AppBarTab to="/">The Coffee Network</AppBarTab>
      <AppBarTab to="/signin">Sign In</AppBarTab>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    backgroundColor: theme.colors.appBarBackground,
    paddingTop: StatusBar.currentHeight + 10,
    paddingBottom: 10,
    paddingLeft: 15,
    paddingRight: 15,
  },
  text: {
    color: theme.colors.cardBackground,
    fontSize: theme.fontSizes.heading,
    fontWeight: theme.fontWeights.bold,
  },
});

export default AppBar;

components / AppBar.jsx

Link

componente interno - AppBar: active link

import { StyleSheet, View, StatusBar, Text, ScrollView } from "react-native";
import { Link, useLocation } from "react-router-native";
import theme from "../theme.js";
import StyledText from "./StyledText.jsx";

const AppBarTab = ({ children, to }) => {
  const { pathname } = useLocation();
  const active = pathname === to;
  const textStyles = [styles.text, active && styles.active];
  return (
    <Link to={to}>
      <StyledText fontWeight="bold" style={textStyles}>
        {children}
      </StyledText>
    </Link>
  );
};
const AppBar = () => {
  return (
    <View style={styles.container}>
      <AppBarTab to="/">The Coffee Network</AppBarTab>
      <AppBarTab to="/signin">Sign In</AppBarTab>
    </View>
  );
};
const styles = StyleSheet.create({
  container: { ...  },
  text: {
    color: theme.colors.cardBackground,
    fontSize: theme.fontSizes.heading,
    fontWeight: theme.fontWeights.bold,
  },
  active: {
    borderBottomColor: theme.colors.background,
    borderBottomWidth: 1,
  },
});

components / AppBar.jsx

Obtiene el path actual

Obtiene el path actual

Genera los estilos del texto

ui dinámica dependiendo de la plataforma

ui dinámica dependiendo de la plataforma

estilo dependiente de plataforma

import React from "react";
import { Image, View, StyleSheet, Platform } from "react-native";
import StyledText from "./StyledText";
import CoffeeItemHeader from "./CoffeeItemHeader";
import theme from "../theme.js";

const CoffeeItem = ({ coffee }) => {
  const platform = Platform.OS === "android" ? "🤖" : "🍏";
  return (
    <View style={styles.container}>
      <Image style={styles.image} source={{ uri: coffee.img }} />
      <CoffeeItemHeader coffee={coffee} />
      <StyledText fontSize="subheading" fontWeight="bold">
        {coffee.brand}, {coffee.country}
      </StyledText>
      <StyledText>
        {platform}: {coffee.body}, {coffee.acidity}, {coffee.notes}
      </StyledText>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    padding: 20,
    backgroundColor: Platform.select({
      android: theme.colors.cardBackground,
      ios: "#FFCC6D",
      default: "purple",
    }),
    marginTop: 6,
    marginBottom: 6,
    marginLeft: 15,
    marginRight: 15,
    borderRadius: 15,
  },
  image: {
    height: 50,
    borderRadius: 4,
    marginBottom: 5,
  },
});

components / CoffeeItem.jsx

Genera un valor dependiendo de la plataforma

Modo más simple: Platform.select

Genera un valor dependiendo de la plataforma

import { Platform } 
from "react-native";

const theme = {
  colors: { ...  },
  fontSizes: { ...  },
  fonts: {
    main: Platform.select({
      ios: "Times New Roman",
      android: "Roboto",
      default: "System",
    }),
  },
  fontWeights: { ... },
};

theme.js

Selección de estilos por plataforma a nivel del theme

cargar un componente por plataforma

const AppBarTab = ({ children, to }) => { ... };

const IOSAppBar = () => { ... };

const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    backgroundColor: "black",
    paddingTop: StatusBar.currentHeight + 10,
    paddingBottom: 10,
    paddingLeft: 15,
    paddingRight: 15,
  },
  text: { ... },
  active: { ... },
});

export default IOSAppBar;

components / IOSAppBar.jsx

Carga dinámica del componente dependiendo de la plataforma

import React from "react";
import { Text, SafeAreaView, StyleSheet, Platform } from "react-native";
import { Route, Routes } from "react-router-native";
import CoffeeList from "./CoffeeList";

const AppBar = Platform.select({
  ios: () => require("./IOSAppBar").default,
  default: () => require("./AppBar").default,
})();

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <AppBar />
      <Routes>
        <Route path="/" element={<CoffeeList />} />
        <Route path="/signin" element={<Text>ABC</Text>} />
      </Routes>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({ ... });

components / Main.jsx

La manera más fácil de cargar el componente

Renombrar IOSAppBar.jsx

AppBar.ios.jsx

Importar el componente SIN la extensión jsx

obtener info del backend

obtener info del backend

obtener información remota

import { useEffect, useState } from "react";
import { FlatList, StyleSheet } from "react-native";
//import { coffees } from "../data/coffees";
import CoffeeItem from "./CoffeeItem";
import theme from "../theme.js";

const CoffeeList = () => {
  const [coffees, setCoffees] = useState(null);
  useEffect(() => {
    const fetchCoffees = async () => {
      const url = "https://64f8eadd824680fd21803236.mockapi.io/coffees";

      try {
        const response = await fetch(url);
        const json = await response.json();
        setCoffees(json);
      } catch (error) {
        console.log(error);
      }
    };
    fetchCoffees();
  }, []);

  return (
    <FlatList
      style={styles.container}
      data={coffees}
      renderItem={({ item }) => <CoffeeItem coffee={item} />}
      keyExtractor={(item) => item.id}
    />
  );
};

components / CoffeeList.jsx

No se obtienen los datos del archivo local

Se obtienen los datos de un endpoint remoto

www.mockapi.com

useState

obtener información remota con un hook

import { FlatList, StyleSheet } from "react-native";
import CoffeeItem from "./CoffeeItem";

import theme from "../theme.js";
import useCoffees from "../hooks/useCoffees";

const CoffeeList = () => {
  const { coffees } = useCoffees();

  return (
    <FlatList
      style={styles.container}
      data={coffees}
      renderItem={({ item }) => <CoffeeItem coffee={item} />}
      keyExtractor={(item) => item.id}
    />
  );
};

const styles = StyleSheet.create({ ... });

export default CoffeeList;

components / CoffeeList.jsx

import { useEffect, useState } from "react";

const useCoffees = () => {
  const [coffees, setCoffees] = useState(null);

  useEffect(() => {
    const fetchCoffees = async () => {
      const url = "https://64f8eadd824680fd21803236.mockapi.io/coffees";

      try {
        const response = await fetch(url);
        const json = await response.json();
        setCoffees(json);
      } catch (error) {
        console.log(error);
      }
    };
    fetchCoffees();
  }, []);

  return { coffees };
};

export default useCoffees;

hooks / useCoffees.js

indicador de actividad

indicador de actividad

indicador de actividad al cargar la página

import { FlatList, StyleSheet, ActivityIndicator } from "react-native";
import { Dimensions } from "react-native";
import CoffeeItem from "./CoffeeItem";
import theme from "../theme.js";
import useCoffees from "../hooks/useCoffees";

const CoffeeList = () => {
  const { coffees } = useCoffees();

  return (
    <>
      {!coffees ? (
        <ActivityIndicator
          size="large"
          color="#ff0000"
          style={styles.loading}
        />
      ) : (
        <FlatList
          style={styles.container}
          data={coffees}
          renderItem={({ item }) => <CoffeeItem coffee={item} />}
          keyExtractor={(item) => item.id}
        />
      )}
    </>
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: theme.colors.background,
  },
  loading: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    height: Dimensions.get("window").height,
  },
});

components / CoffeeList.jsx

ActivityIndicator

estilo del indicador

Expresión ternaria

uso de variables de ambiente en react native

npm install react-native-dotenv

Instalación del paquete NodeJS

{
  "plugins": [
    [
      "module:react-native-dotenv",
      {
        "envName": "APP_ENV",
        "moduleName": "@env",
        "path": ".env"
      }
    ]
  ]
}

Variables de Ambiente con Expo

REACT_APP_URL_API=https://64f8eadd824680fd21803236.mockapi.io/coffees

.env

import { useEffect, useState } from "react";
import { REACT_APP_URL_API } from "@env";

const useCoffees = () => {
  const [coffees, setCoffees] = useState(null);

  useEffect(() => {
    const fetchCoffees = async () => {
      try {
        const response = await fetch(REACT_APP_URL_API);
        const json = await response.json();
        setCoffees(json);
      } catch (error) {
        console.log(error);
      }
    };
    fetchCoffees();
  }, []);

  return { coffees };
};
export default useCoffees;

hooks / useCoffees.js

.babelrc

EXPO_PUBLIC_URL_API=https://64f8eadd824680fd21803236.mockapi.io/coffees
const response = await fetch(process.env.EXPO_PUBLIC_URL_API);

formularios

formularios

formularios: formik - agregar café - ruta

import AddCoffee from "./AddCoffee";

const Main = () => {
  return (
    <SafeAreaView style={styles.container}>
      <AppBar />
      <Routes>
        <Route path="/" element={<CoffeeList />} />
        <Route path="/signin" element={<AddCoffee />} />
      </Routes>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({ ... });

export default Main;

components / Main.jsx

npm install formik

Instalación del paquete NodeJS

Nueva ruta que contendrá el formulario

formularios: formik - agregar café - Página

import { View, Button, StyleSheet, TextInput } from "react-native";
import { Formik } from "formik";
const initialValues = {
  product: "",
  brand: "",
  country: "",
  body: "",
  acidity: "",
  notes: "",
  stars: "",
  img: "",
};
const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={(values) => console.log(values)}>
      {({ handleChange, handleSubmit, values }) => {
        return (
          <View style={styles.form}>
            <TextInput placeholder="product" value={values.product} onChangeText={handleChange("product")} />
            <TextInput placeholder="brand" value={values.brand} onChangeText={handleChange("brand")} />
            <TextInput placeholder="country" value={values.country} onChangeText={handleChange("country")} />
            <TextInput placeholder="body" value={values.body} onChangeText={handleChange("body")} />
            <TextInput placeholder="acidity" value={values.acidity} onChangeText={handleChange("acidity")} />
            <TextInput placeholder="notes" value={values.notes} onChangeText={handleChange("notes")} />
            <TextInput placeholder="stars" value={values.stars} onChangeText={handleChange("stars")} />
            <TextInput placeholder="image" value={values.img} onChangeText={handleChange("img")} />
            <Button onPress={handleSubmit} title="Add coffee" />
          </View>
        );
      }}
    </Formik>
  );
};
const styles = StyleSheet.create({
  form: {
    margin: 12,
  },
});
export default AddCoffee;

components / AddCoffee.jsx

onSubmit

Función interna de Formik

eventos

onSubmit

onPress

onChangeText

Valores iniciales

touchableopacity: botón personalizable

import { View, Button, StyleSheet, TextInput } from "react-native";
import { Formik } from "formik";
const initialValues = { ... };
const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={(values) => console.log(values)}>
      {({ handleChange, handleSubmit, values }) => {
        return (
          <View style={styles.form}>
           ...
            <TouchableOpacity style={styles.button} onPress={handleSubmit}>
              <Text style={styles.button.text}>Add Coffee</Text>
            </TouchableOpacity>
          </View>
        );
      }}
    </Formik>
  );
};
const styles = StyleSheet.create({
  form: {
    margin: 12,
  },
  button: {
    backgroundColor: theme.colors.appBarBackground,
    color: "#fff",
    padding: 8,
    borderRadius: 5,
    marginTop: 10,
    text: {
      color: "#fff",
      textAlign: "center",
      fontSize: 15,
    },
  },
});
export default AddCoffee;

components / AddCoffee.jsx

TouchableOpacity necesita Text

Se pueden utilizar estilos anidados

formularios: styled text input

import React from "react";
import { StyleSheet, TextInput } from "react-native";

const StyledTextInput = ({ style = {}, ...props }) => {
  const inputStyle = {
    ...styles.textInput,
    ...style,
  };
  return <TextInput style={inputStyle} {...props} />;
};

const styles = StyleSheet.create({
  textInput: {
    borderRadius: 5,
    borderWidth: 1,
    borderColor: "#999",
    paddingHorizontal: 20,
    paddingVertical: 10,
    marginBottom: 10,
  },
});
export default StyledTextInput;

components / StyledTextInput.jsx

import { View, Button, StyleSheet, TextInput } from "react-native";
import { Formik } from "formik";
import StyledTextInput from "./StyledTextInput";

const initialValues = { ... };

const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={(values) => console.log(values)} >
      {({ handleChange, handleSubmit, values }) => {
        return (
          <View style={styles.form}>
            <StyledTextInput
              placeholder="product"
              value={values.product}
              onChangeText={handleChange("product")}
            />
            <StyledTextInput
              placeholder="brand"
              value={values.brand}
              onChangeText={handleChange("brand")}
            />
            ...
            <Button onPress={handleSubmit} title="Add coffee" />
          </View>
        );
      }}
    </Formik>
  );
};
const styles = StyleSheet.create({
  form: {
    margin: 12,
  },
});

components / AddCoffee.jsx

Se reemplaza el TextInput por StyledTextInput

formularios: refactor de campos

import { Formik, useField } from "formik";

const initialValues = { ... };

const FormikInputValue = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  return (
    <StyledTextInput
      value={field.value}
      onChangeText={(value) => helpers.setValue(value)}
      {...props}
    />
  );
};

const Login = () => {
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={(values) => console.log(values)} >
      {({ handleSubmit }) => {
        return (
          <View style={styles.form}>
            <FormikInputValue placeholder="product" name="product" />
            <FormikInputValue placeholder="brand" name="brand" />
            <FormikInputValue placeholder="country" name="country" />
            <FormikInputValue placeholder="body" name="body" />
            <FormikInputValue placeholder="acidity" name="acidity" />
            <FormikInputValue placeholder="notes" name="notes" />
            <FormikInputValue placeholder="stars" name="stars" />
            <FormikInputValue placeholder="image" name="img" />
            <Button onPress={handleSubmit} title="Add coffee" />
          </View>
        );
      }}    
    </Formik>
  );
};

components / AddCoffee.jsx

Creación de componente de manejo de datos de los campos del formulario

Uso del componente

Sólo se require el name y el placeholder

Solo se requiere handleSubmit

Se cambia StyledTextInput por FormikInputValue

secureTextEntry

atributo

passwords

validación de formularios

validación de formularios

formularios: validación manual - i

import StyledText from "./StyledText";
const FormikInputValue = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  return (
    <>
      <StyledTextInput
        value={field.value}
        onChangeText={(value) => helpers.setValue(value)}
        {...props} />
      {meta.error && <StyledText style={styles.error}>{meta.error}</StyledText>}
    </>
  );
};
const validate = (values) => {
  const errors = {};
  if (!values.product) {
    errors.product = "Product is required";
  }
  if (!values.country) {
    errors.country = "Country is required";
  }
  if (!/^\+?(0|[1-9]\d*)$/.test(values.stars)) {
    errors.stars = "Numeric value is required";
  }
  return errors;
};
const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      validate={validate}
      onSubmit={(values) => console.log(values)} >
      { ... }
    </Formik>
  );
};

components / AddCoffee.jsx

Muestra el mensaje de validación

Uso de la función de validación

Validación del campo - Mostrar mensaje

Función de validación

formularios: validación manual - iI

const initialValues = { ... };

const FormikInputValue = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  return (
    <>
      <StyledTextInput
        error={meta.error}
        value={field.value}
        onChangeText={(value) => helpers.setValue(value)}
        {...props}
      />
      {meta.error && <StyledText style={styles.error}>{meta.error}</StyledText>}
    </>
  );
};
const validate = (values) => { ... };
const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      validate={validate}
      onSubmit={(values) => console.log(values)} >
       { ... }
  </Formik>
  );
};

const styles = StyleSheet.create({
  form: {
    margin: 12,
  },
  error: {
    color: "red",
    marginBottom: 15,
    marginTop: -5,
  },
});

components / AddCoffee.jsx

Parámetro de error

const StyledTextInput = ({ style = {}, error, ...props }) => {
  const inputStyle = 
        [
          styles.textInput, 
          style, 
          error && styles.error
        ];
  return <TextInput style={inputStyle} {...props} />;
};

const styles = StyleSheet.create({
  textInput: {
    borderRadius: 5,
    borderWidth: 1,
    borderColor: "#999",
    paddingHorizontal: 20,
    paddingVertical: 10,
    marginBottom: 10,
  },
  error: {
    borderColor: "red",
  },
});

components / StyledtextInput.jsx

Validación del campos - Bordes de error en el Input

formularios: validación con yup

import { coffeeValidationSchema } from "../schemas/coffeeValidation";

const initialValues = { ... };

const FormikInputValue = ({ name, ...props }) => {
  const [field, meta, helpers] = useField(name);
  return (
    <>
      <StyledTextInput
        error={meta.error}
        value={field.value}
        onChangeText={(value) => helpers.setValue(value)}
        {...props}
      />
      {meta.error && <StyledText style={styles.error}>{meta.error}</StyledText>}
    </>
  );
};

const AddCoffee = () => {
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={coffeeValidationSchema}
      onSubmit={(values) => console.log(values)} >
      { ... }
    </Formik>
  );
};

components / AddCoffee.jsx

npm install yup

Instalación del paquete NodeJS

import * as yup from "yup";

export const coffeeValidationSchema = 
  yup
    .object()
    .shape({
    product: 
      yup
        .string()
        .required("Please type a product"),
    brand: 
      yup
        .string()
        .required(),
    country: 
      yup
        .string()
        .oneOf(
          [
            "Colombia", 
            "Kenia", 
            "Camerun", 
            "Brazil"]),
    stars: 
      yup
        .number(),
});

schemas / coffeeValidation.js

Establece el schema de validación

min, max, required, number, string, oneOf, matches, email

formularios: slider

import { useState } from "react";
import Slider from "@react-native-community/slider";

const AddCoffee = () => {
  const [sliderValue, setSliderValue] = useState(3);
  const addCoffee = async (values) => {
    const data = {
      ...values,
      img: selectedImage,
      stars: parseInt(sliderValue),
    };
    try { ... } catch (error) {
      console.log(error);
    }
  };  
  return (
    <Formik onSubmit={addCoffee}>
      {({ handleSubmit }) => {
        return (
          <View style={styles.form}>
            <Text>Stars: {sliderValue}</Text>
            <Slider
              style={{ width: "100%", height: 40 }}
              minimumValue={0}
              maximumValue={5}
              onValueChange={(value) => setSliderValue(value)}
              value={sliderValue} />
          </View>
        );
      }}
    </Formik>
  );
};

components / AddCoffee.jsx

npx expo install @react-native-community/slider

Instalación del paquete NodeJS

Uso del Slider

Obtiene el valor seleccionado

Muestra el valor seleccionado

formularios: dropdown select list

import { useState } from "react";
import { SelectList } from "react-native-dropdown-select-list";
const countries = [
  { key: "1", value: "Colombia" },
  { key: "2", value: "Kenia" },
  { key: "3", value: "Camerun" },
  { key: "4", value: "Brazil" },
];
const AddCoffee = () => {
  const [selectedCountry, setSelectedCountry] = useState("");
  const addCoffee = async (values) => {
    const data = {
      ...values,
      country: selectedCountry,
      img: selectedImage,
      stars: parseInt(sliderValue),
    };
    ...
  };
  return (
    <Formik>
      {({ handleSubmit }) => {
        return (
          <View style={styles.form}>
            <SelectList
              placeholder="Country"
              setSelected={(val) => setSelectedCountry(val)}
              data={countries}
              save="value"
              boxStyles={{ marginBottom: 15 }}
            />
          </View>
        );
      }}
    </Formik>
  );
};

components / AddCoffee.jsx

npm install react-native-dropdown-select-list

Instalación del paquete NodeJS

Uso del Dropdown Select List

Obtiene el valor seleccionado

envío de datos al backend

envío de datos al backend

Envío de datos al backend

import { coffeeValidationSchema } from "../schemas/coffeeValidation";
import { REACT_APP_URL_API } from "@env";
import { useNavigate } from "react-router-native";
const initialValues = { ... };
const FormikInputValue = ({ name, ...props }) => { ... };
const AddCoffee = () => {
  const navigate = useNavigate();

  const addCoffee = async (values) => {
    const data = { ...values, stars: parseInt(values.stars) };
    try {
      await fetch(REACT_APP_URL_API, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      });
      navigate("/");
    } catch (error) {
      console.log(error);
    }
  };
  
  return (
    <Formik
      initialValues={initialValues}
      validationSchema={coffeeValidationSchema}
      onSubmit={addCoffee}>
      ...
    </Formik>
  );
};

components / AddCoffee.jsx

Uso de variable de entorno

Función de envío de datos

Navegación al home

Conversión de datos

seleccionar una imagen local

import { Text, View, Button, StyleSheet, TouchableOpacity } from "react-native";
import * as ImagePicker from "expo-image-picker";
import { useState } from "react";
const AddCoffee = () => {
  const navigate = useNavigate();
  const [selectedImage, setSelectedImage] = useState(null);
  const openImagePicker = async () => {
    let permissionResult = await ImagePicker.requestCameraPermissionsAsync();
    if (permissionResult.granted === false) {
      alert("Permission to camera/images is required");
      return;
    }
    const pickerResult = await ImagePicker.launchImageLibraryAsync();
    if (!pickerResult.canceled) {
      setSelectedImage(pickerResult.assets[0].uri);
    }
  };
  const addCoffee = async (values) => {
    const data = {
      ...values,
      img: selectedImage,
      stars: parseInt(values.stars),
    };
    ...
  };
  return (
    <Formik onSubmit={addCoffee} >
      {({ handleSubmit }) => {
        return (
          <View style={styles.form}>
            ...
            <Button onPress={openImagePicker} title="Select image..." />
            <TouchableOpacity style={styles.button} onPress={handleSubmit}>
              <Text style={styles.button.text}>Add Coffee</Text>
            </TouchableOpacity>
          </View>
        );
      }}
    </Formik>
  );
};

components / AddCoffee.jsx

npx expo install expo-image-picker

Instalación del paquete NodeJS

Función para mostrar el ImagePicker

useState para almacenar la ruta de la imagen

splash screen + icono

splash screen + icono

establecer imagen splash & icono

{
  "expo": {
    "name": "our-coffee",
    "slug": "our-coffee",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "splash": {
      "image": "./assets/fnc-splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#B00025"
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    }
  }
}

App.json

Copiar las imágenes de Splash y el ícono en el folder assets y referenciarlas

1284 x 2778

Splash

Ícono

1024 x 1024

generar aplicación

generar aplicación

generar aplicación

npx expo run:android

Instrucción para generar la aplicación

npx expo run:ios

Creación de cuenta gratis

Java

XCode

java -version

Verificar la instalación de Java

Crear proyecto

npm install --global eas-cli
eas init --id identificador

Se crea un nuevo proyecto en el sitio web y se toman los siguientes comandos

Documentación de Building

john cardozo

johncardozo@gmail.com

React Native

By John Cardozo

React Native

  • 89