Context y Provider para estado global de hábitos

Clase 15 de 22Curso de Fundamentos de React Native

Contenido del curso

Módulo 3: Interactividad y Manejo de Datos

Resumen

Crear un estado global de hábitos confiable en React es más sencillo si organizas bien el context, el provider y las actualizaciones con useReducer y useEffect. Aquí verás cómo hidratar el estado desde AsyncStorage, cómo persistir cambios sin errores y cómo preparar las acciones de agregar y seleccionar hábitos. Todo, paso a paso, con buenas prácticas y mensajes de control en consola.

¿Cómo definir el context y el provider en React?

Para centralizar la lógica, se define un tipo que agrupe hábitos, estado de carga y funcionalidades. Luego, se crea el context con ese tipo y un valor por defecto nulo si no está disponible. El provider expone el estado y las funciones, devolviendo un ReactNode.

  • Define un tipo con hábitos, loading y funciones de negocio.
  • Crea el context con createContext y usa el tipo o null como valor inicial.
  • Implementa el provider que retornará el Provider del contexto.
  • Integra useReducer con un reducer y un initialState.
// Esquema orientativo import React, { createContext, useReducer, useEffect } from 'react'; type HabitsContextType = { habits: any[]; loading: boolean; addHabit: (...args: any[]) => void; selectHabit: (...args: any[]) => void; }; const HabitsContext = createContext<HabitsContextType | null>(null); function HabitsProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(reducer, initialState); // acciones se implementan más abajo en el flujo const addHabit = (...args: any[]) => dispatch({ type: 'add', payload: args }); const selectHabit = (...args: any[]) => dispatch({ type: 'select', payload: args }); return ( <HabitsContext.Provider value={{ habits: state.habits, loading: state.loading, addHabit, selectHabit }}> {children} </HabitsContext.Provider> ); }

Clave: el useReducer retorna el estado y una función de actualización (dispatch), que permite ejecutar acciones con su payload.

¿Cómo hidratar el estado con AsyncStorage y useEffect?

La hidratación carga los hábitos guardados en la “memoria de la aplicación” con AsyncStorage. Se usa useEffect asíncrono, con try/catch, para leer por la clave definida (por ejemplo, storageKey), parsear el JSON y despachar la acción de hidratar.

  • Usa un useEffect inicial para leer del almacenamiento.
  • Llama a getItem con la key definida: storageKey.
  • Convierte el “raw” a objeto con JSON.parse.
  • Despacha { type: 'hydrate', payload } con los datos.
  • Maneja errores con console.warn: "No se pudo cargar hábitos".
useEffect(() => { (async () => { try { const raw = await AsyncStorage.getItem(storageKey); if (!raw) return; // sin datos, no se hidrata const data = JSON.parse(raw); dispatch({ type: 'hydrate', payload: data }); } catch (e) { console.warn('No se pudo cargar hábitos'); // opcional: asegurar un estado válido si algo falla dispatch({ type: 'hydrate', payload: [] }); } })(); }, []);

Importante: si no existe el tipo o la data, se devuelve null o un estado vacío, evitando rupturas en la UI.

¿Cómo guardar cambios y sincronizar hábitos sin errores?

Cada vez que cambian los hábitos, se debe persistir el estado. Para evitar escrituras redundantes, se usa un temporizador de guardado (save timer) y se limpia con clearTimeout antes de programar un nuevo guardado. Además, si el estado está loading, se evita guardar.

  • Omite el guardado si state.loading es true.
  • Usa un temporizador para controlar escrituras.
  • Limpia con clearTimeout antes de programar otra operación.
  • Guarda con AsyncStorage.setItem y JSON.stringify del estado.
  • Controla errores: console.warn("No se pudo guardar").
  • Devuelve una limpieza que cancele el temporizador activo.
const saveTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { if (state.loading) return; // no guardar mientras hidrata if (saveTimer.current) clearTimeout(saveTimer.current); saveTimer.current = setTimeout(async () => { try { await AsyncStorage.setItem(storageKey, JSON.stringify(state.habits)); } catch (e) { console.warn('No se pudo guardar'); } }); return () => { if (saveTimer.current) clearTimeout(saveTimer.current); }; }, [state.habits, state.loading]);

¿Qué acciones del reducer se exponen en el provider?

Al final, el provider expone las funciones conectadas al dispatch para operar sobre el estado global.

  • Acción "hydrate": repuebla el estado con los hábitos cargados.
  • Acción "add": agrega un hábito al listado.
  • Acción "select": marca o selecciona un hábito.

Resultado: el context actúa como “centro de mando”, entregando estado y funcionalidades listas para usar en la aplicación.

¿Te gustaría ver cómo implementar y consumir este provider en tus componentes? Comparte tus dudas o casos y seguimos construyendo juntos.