En este proyecto, vamos a aplicar los conocimientos adquiridos en los módulos anteriores para construir una aplicación de lista de tareas (To-Do List) utilizando React Native. Este proyecto te permitirá consolidar conceptos clave como componentes, estado, ciclo de vida, manejo de eventos, y almacenamiento local.
Objetivos del Proyecto
- Crear una interfaz de usuario para agregar, visualizar y eliminar tareas.
- Manejar el estado de la aplicación para gestionar las tareas.
- Almacenar las tareas localmente utilizando AsyncStorage.
- Implementar un diseño responsivo y atractivo.
Estructura del Proyecto
- Configuración Inicial
- Creación de Componentes
- Manejo del Estado
- Persistencia de Datos con AsyncStorage
- Estilizado de la Aplicación
- Pruebas y Depuración
- Configuración Inicial
Crear un Nuevo Proyecto
Primero, crea un nuevo proyecto de React Native utilizando npx react-native init
:
Instalar Dependencias Necesarias
Instalaremos @react-native-async-storage/async-storage
para manejar el almacenamiento local:
- Creación de Componentes
Estructura de Archivos
Organiza tu proyecto creando una carpeta components
para almacenar los componentes reutilizables:
Componente TaskInput
Este componente manejará la entrada del usuario para agregar nuevas tareas.
// components/TaskInput.js import React, { useState } from 'react'; import { View, TextInput, Button, StyleSheet } from 'react-native'; const TaskInput = ({ onAddTask }) => { const [task, setTask] = useState(''); const handleAddTask = () => { if (task.trim()) { onAddTask(task); setTask(''); } }; return ( <View style={styles.inputContainer}> <TextInput placeholder="Add a new task" style={styles.input} value={task} onChangeText={setTask} /> <Button title="Add" onPress={handleAddTask} /> </View> ); }; const styles = StyleSheet.create({ inputContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, }, input: { width: '80%', borderBottomWidth: 1, borderBottomColor: '#ccc', padding: 10, }, }); export default TaskInput;
Componente TaskItem
Este componente representará cada tarea en la lista.
// components/TaskItem.js import React from 'react'; import { View, Text, Button, StyleSheet } from 'react-native'; const TaskItem = ({ task, onDelete }) => { return ( <View style={styles.taskItem}> <Text>{task}</Text> <Button title="Delete" onPress={onDelete} /> </View> ); }; const styles = StyleSheet.create({ taskItem: { padding: 10, backgroundColor: '#f9f9f9', borderBottomWidth: 1, borderBottomColor: '#ddd', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, }); export default TaskItem;
- Manejo del Estado
Integrar Componentes en App.js
Vamos a integrar los componentes TaskInput
y TaskItem
en App.js
y manejar el estado de las tareas.
// App.js import React, { useState } from 'react'; import { View, FlatList, StyleSheet } from 'react-native'; import TaskInput from './components/TaskInput'; import TaskItem from './components/TaskItem'; const App = () => { const [tasks, setTasks] = useState([]); const addTask = (task) => { setTasks((currentTasks) => [ ...currentTasks, { id: Math.random().toString(), value: task }, ]); }; const deleteTask = (taskId) => { setTasks((currentTasks) => { return currentTasks.filter((task) => task.id !== taskId); }); }; return ( <View style={styles.screen}> <TaskInput onAddTask={addTask} /> <FlatList data={tasks} renderItem={(itemData) => ( <TaskItem task={itemData.item.value} onDelete={() => deleteTask(itemData.item.id)} /> )} keyExtractor={(item) => item.id} /> </View> ); }; const styles = StyleSheet.create({ screen: { padding: 50, }, }); export default App;
- Persistencia de Datos con AsyncStorage
Guardar y Cargar Tareas
Vamos a modificar App.js
para guardar y cargar las tareas utilizando AsyncStorage
.
// App.js import React, { useState, useEffect } from 'react'; import { View, FlatList, StyleSheet } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import TaskInput from './components/TaskInput'; import TaskItem from './components/TaskItem'; const App = () => { const [tasks, setTasks] = useState([]); useEffect(() => { loadTasks(); }, []); useEffect(() => { saveTasks(); }, [tasks]); const saveTasks = async () => { try { await AsyncStorage.setItem('tasks', JSON.stringify(tasks)); } catch (error) { console.error('Failed to save tasks', error); } }; const loadTasks = async () => { try { const savedTasks = await AsyncStorage.getItem('tasks'); if (savedTasks) { setTasks(JSON.parse(savedTasks)); } } catch (error) { console.error('Failed to load tasks', error); } }; const addTask = (task) => { setTasks((currentTasks) => [ ...currentTasks, { id: Math.random().toString(), value: task }, ]); }; const deleteTask = (taskId) => { setTasks((currentTasks) => { return currentTasks.filter((task) => task.id !== taskId); }); }; return ( <View style={styles.screen}> <TaskInput onAddTask={addTask} /> <FlatList data={tasks} renderItem={(itemData) => ( <TaskItem task={itemData.item.value} onDelete={() => deleteTask(itemData.item.id)} /> )} keyExtractor={(item) => item.id} /> </View> ); }; const styles = StyleSheet.create({ screen: { padding: 50, }, }); export default App;
- Estilizado de la Aplicación
Mejorar el Estilo
Vamos a mejorar el estilo de la aplicación para que sea más atractiva.
// components/TaskInput.js import React, { useState } from 'react'; import { View, TextInput, Button, StyleSheet } from 'react-native'; const TaskInput = ({ onAddTask }) => { const [task, setTask] = useState(''); const handleAddTask = () => { if (task.trim()) { onAddTask(task); setTask(''); } }; return ( <View style={styles.inputContainer}> <TextInput placeholder="Add a new task" style={styles.input} value={task} onChangeText={setTask} /> <Button title="Add" onPress={handleAddTask} /> </View> ); }; const styles = StyleSheet.create({ inputContainer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, borderBottomWidth: 1, borderBottomColor: '#ccc', padding: 10, }, input: { width: '80%', padding: 10, }, }); export default TaskInput;
// components/TaskItem.js import React from 'react'; import { View, Text, Button, StyleSheet } from 'react-native'; const TaskItem = ({ task, onDelete }) => { return ( <View style={styles.taskItem}> <Text>{task}</Text> <Button title="Delete" onPress={onDelete} /> </View> ); }; const styles = StyleSheet.create({ taskItem: { padding: 10, backgroundColor: '#f9f9f9', borderBottomWidth: 1, borderBottomColor: '#ddd', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, }); export default TaskItem;
// App.js import React, { useState, useEffect } from 'react'; import { View, FlatList, StyleSheet } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import TaskInput from './components/TaskInput'; import TaskItem from './components/TaskItem'; const App = () => { const [tasks, setTasks] = useState([]); useEffect(() => { loadTasks(); }, []); useEffect(() => { saveTasks(); }, [tasks]); const saveTasks = async () => { try { await AsyncStorage.setItem('tasks', JSON.stringify(tasks)); } catch (error) { console.error('Failed to save tasks', error); } }; const loadTasks = async () => { try { const savedTasks = await AsyncStorage.getItem('tasks'); if (savedTasks) { setTasks(JSON.parse(savedTasks)); } } catch (error) { console.error('Failed to load tasks', error); } }; const addTask = (task) => { setTasks((currentTasks) => [ ...currentTasks, { id: Math.random().toString(), value: task }, ]); }; const deleteTask = (taskId) => { setTasks((currentTasks) => { return currentTasks.filter((task) => task.id !== taskId); }); }; return ( <View style={styles.screen}> <TaskInput onAddTask={addTask} /> <FlatList data={tasks} renderItem={(itemData) => ( <TaskItem task={itemData.item.value} onDelete={() => deleteTask(itemData.item.id)} /> )} keyExtractor={(item) => item.id} /> </View> ); }; const styles = StyleSheet.create({ screen: { padding: 50, backgroundColor: '#f5f5f5', flex: 1, }, }); export default App;
- Pruebas y Depuración
Probar la Aplicación
- Asegúrate de que puedes agregar, visualizar y eliminar tareas.
- Verifica que las tareas se guardan y cargan correctamente al reiniciar la aplicación.
Depuración
- Utiliza
console.log
para depurar problemas. - Asegúrate de manejar errores en las operaciones de AsyncStorage.
Conclusión
En este proyecto, hemos construido una aplicación de lista de tareas completa utilizando React Native. Hemos cubierto la creación de componentes, manejo del estado, persistencia de datos con AsyncStorage, y estilizado de la aplicación. Este proyecto te ha proporcionado una base sólida para construir aplicaciones más complejas en el futuro.
¡Felicidades por completar el Proyecto 1! Ahora estás listo para avanzar al siguiente proyecto y seguir mejorando tus habilidades en React Native.
Curso de React Native
Módulo 1: Introducción a React Native
- ¿Qué es React Native?
- Configuración del Entorno de Desarrollo
- Aplicación Hola Mundo
- Entendiendo JSX
- Componentes y Props
Módulo 2: Componentes Básicos y Estilizado
- Visión General de Componentes Básicos
- Texto, Vista e Imagen
- Estilizado con Flexbox
- Manejo de Entrada del Usuario
- ScrollView y ListView
Módulo 3: Estado y Ciclo de Vida
- Métodos de Estado y Ciclo de Vida
- Manejo de Eventos
- Renderizado Condicional
- Listas y Claves
- Formularios y Componentes Controlados
Módulo 4: Navegación
- Introducción a React Navigation
- Navegador de Pila
- Navegador de Pestañas
- Navegador de Cajón
- Pasando Parámetros a Rutas
Módulo 5: Redes y Datos
- Obteniendo Datos con Fetch API
- Usando Axios para Solicitudes HTTP
- Manejo de Errores de Red
- AsyncStorage para Datos Locales
- Integración con APIs REST
Módulo 6: Conceptos Avanzados
Módulo 7: Despliegue y Publicación
- Construyendo para iOS
- Construyendo para Android
- Publicando en App Store
- Publicando en Google Play
- Integración y Entrega Continua