ReactFire

Conecta React con Firebase de manera sencilla usando hooks

ReactFire es una librería que te permite usar Firebase en tus aplicaciones React de manera fácil y natural usando hooks. Perfecta para estudiantes y desarrolladores que quieren integrar autenticación, base de datos y almacenamiento en sus proyectos.

🎣 Hooks de React
🔥 Firebase SDK v9+
TypeScript
📱 Tiempo Real

📦 Instalación

Para empezar a usar ReactFire en tu proyecto, necesitas instalarlo junto con Firebase:

Con npm:

npm install reactfire firebase

Con yarn:

yarn add reactfire firebase

💡 Nota: ReactFire requiere React 16.8+ para usar hooks y Firebase SDK v9+

⚙️ Configuración Inicial

Antes de usar ReactFire, necesitas configurar tu aplicación con Firebase:

1. Obtén tu configuración de Firebase

Ve a la consola de Firebase y crea un proyecto. Luego obtén tu configuración desde la configuración del proyecto.

2. Configura ReactFire en tu aplicación

// App.tsx
import React from 'react';
import { FirebaseAppProvider } from 'reactfire';
import { MiComponente } from './MiComponente';

// Tu configuración de Firebase
const firebaseConfig = {
  apiKey: "tu-api-key",
  authDomain: "tu-proyecto.firebaseapp.com",
  projectId: "tu-proyecto",
  storageBucket: "tu-proyecto.appspot.com",
  messagingSenderId: "123456789",
  appId: "tu-app-id"
};

function App() {
  return (
    <FirebaseAppProvider firebaseConfig={firebaseConfig}>
      <MiComponente />
    </FirebaseAppProvider>
  );
}

export default App;

3. Configura los proveedores de servicios

// Configuradores.tsx
import React from 'react';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
import { 
  AuthProvider, 
  FirestoreProvider, 
  StorageProvider, 
  useFirebaseApp 
} from 'reactfire';

function ConfiguradorServicios({ children }) {
  const app = useFirebaseApp();
  
  // Inicializar servicios
  const auth = getAuth(app);
  const firestore = getFirestore(app);
  const storage = getStorage(app);

  return (
    <AuthProvider sdk={auth}>
      <FirestoreProvider sdk={firestore}>
        <StorageProvider sdk={storage}>
          {children}
        </StorageProvider>
      </FirestoreProvider>
    </AuthProvider>
  );
}

🔐 Autenticación

ReactFire hace que manejar la autenticación sea súper fácil con hooks intuitivos.

Hooks principales para autenticación:

  • useAuth() - Obtiene la instancia de Auth
  • useUser() - Obtiene el usuario actual (con Suspense)
  • useSigninCheck() - Verifica si hay un usuario autenticado

Ejemplo: Componente de Login/Logout

import React from 'react';
import { useAuth, useSigninCheck } from 'reactfire';
import { GoogleAuthProvider, signInWithPopup, signOut } from 'firebase/auth';

function ComponenteAuth() {
  const auth = useAuth();
  const { data: signInCheckResult } = useSigninCheck();

  // Función para iniciar sesión con Google
  const iniciarSesion = async () => {
    const provider = new GoogleAuthProvider();
    try {
      await signInWithPopup(auth, provider);
      console.log('¡Sesión iniciada!');
    } catch (error) {
      console.error('Error al iniciar sesión:', error);
    }
  };

  // Función para cerrar sesión
  const cerrarSesion = () => {
    signOut(auth);
  };

  // Si está cargando
  if (signInCheckResult === undefined) {
    return <div>Verificando autenticación...</div>;
  }

  // Si está autenticado
  if (signInCheckResult.signedIn) {
    return (
      <div>
        <h3>¡Bienvenido!</h3>
        <button onClick={cerrarSesion}>
          Cerrar Sesión
        </button>
      </div>
    );
  }

  // Si no está autenticado
  return (
    <div>
      <h3>Por favor, inicia sesión</h3>
      <button onClick={iniciarSesion}>
        Iniciar con Google
      </button>
    </div>
  );
}

Ejemplo: Mostrar información del usuario

import React, { Suspense } from 'react';
import { useUser } from 'reactfire';

function DetallesUsuario() {
  const { data: user } = useUser();

  return (
    <div>
      <h3>Información del Usuario</h3>
      <p><strong>Nombre:</strong> {user.displayName}</p>
      <p><strong>Email:</strong> {user.email}</p>
      <img src={user.photoURL} alt="Foto de perfil" />
    </div>
  );
}

// Úsalo con Suspense
function AppConUsuario() {
  return (
    <Suspense fallback={<div>Cargando usuario...</div>}>
      <DetallesUsuario />
    </Suspense>
  );
}

Ejemplo: Proteger rutas

import React from 'react';
import { useSigninCheck } from 'reactfire';

function RutaProtegida({ children }) {
  const { data: signInCheckResult } = useSigninCheck();

  if (!signInCheckResult.signedIn) {
    return <div>Debes iniciar sesión para ver este contenido</div>;
  }

  return children;
}

// Uso
function App() {
  return (
    <RutaProtegida>
      <ComponentePrivado />
    </RutaProtegida>
  );
}

🗄️ Cloud Firestore

Firestore es la base de datos NoSQL de Firebase. ReactFire te permite leer y escribir datos de forma reactiva.

Hooks principales para Firestore:

  • useFirestore() - Obtiene la instancia de Firestore
  • useFirestoreDocData() - Lee un documento en tiempo real
  • useFirestoreCollectionData() - Lee una colección en tiempo real
  • useFirestoreDocDataOnce() - Lee un documento una sola vez

Ejemplo: Leer un documento

import React, { Suspense } from 'react';
import { useFirestore, useFirestoreDocData } from 'reactfire';
import { doc } from 'firebase/firestore';

function PerfilUsuario({ userId }) {
  const firestore = useFirestore();
  
  // Referencia al documento
  const perfilRef = doc(firestore, 'usuarios', userId);
  
  // Hook para leer el documento en tiempo real
  const { data: perfil } = useFirestoreDocData(perfilRef);

  return (
    <div>
      <h3>Perfil de {perfil.nombre}</h3>
      <p><strong>Email:</strong> {perfil.email}</p>
      <p><strong>Edad:</strong> {perfil.edad}</p>
    </div>
  );
}

// Usar con Suspense
function AppPerfil() {
  return (
    <Suspense fallback={<div>Cargando perfil...</div>}>
      <PerfilUsuario userId="user123" />
    </Suspense>
  );
}

Ejemplo: Leer una colección

import React, { Suspense } from 'react';
import { useFirestore, useFirestoreCollectionData } from 'reactfire';
import { collection, query, orderBy, limit } from 'firebase/firestore';

function ListaPublicaciones() {
  const firestore = useFirestore();
  
  // Crear consulta
  const publicacionesQuery = query(
    collection(firestore, 'publicaciones'),
    orderBy('fechaCreacion', 'desc'),
    limit(10)
  );
  
  // Hook para leer la colección
  const { data: publicaciones } = useFirestoreCollectionData(publicacionesQuery, {
    idField: 'id' // Incluye el ID del documento
  });

  return (
    <div>
      <h3>Últimas Publicaciones</h3>
      {publicaciones.map(publicacion => (
        <div key={publicacion.id} className="publicacion">
          <h4>{publicacion.titulo}</h4>
          <p>{publicacion.contenido}</p>
          <small>Por: {publicacion.autor}</small>
        </div>
      ))}
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<div>Cargando publicaciones...</div>}>
      <ListaPublicaciones />
    </Suspense>
  );
}

Ejemplo: Escribir datos

import React, { useState } from 'react';
import { useFirestore } from 'reactfire';
import { doc, setDoc, addDoc, collection, updateDoc, increment } from 'firebase/firestore';

function EscribirDatos() {
  const firestore = useFirestore();
  const [mensaje, setMensaje] = useState('');

  // Crear un nuevo documento con ID automático
  const crearPublicacion = async () => {
    try {
      const docRef = await addDoc(collection(firestore, 'publicaciones'), {
        titulo: 'Mi nueva publicación',
        contenido: mensaje,
        autor: 'Usuario Actual',
        fechaCreacion: new Date()
      });
      console.log('Documento creado con ID:', docRef.id);
      setMensaje('');
    } catch (error) {
      console.error('Error al crear publicación:', error);
    }
  };

  // Actualizar un documento existente
  const actualizarContador = async () => {
    const contadorRef = doc(firestore, 'contadores', 'general');
    try {
      await updateDoc(contadorRef, {
        visitas: increment(1)
      });
      console.log('Contador actualizado');
    } catch (error) {
      console.error('Error al actualizar contador:', error);
    }
  };

  return (
    <div>
      <h3>Escribir Datos</h3>
      <textarea 
        value={mensaje}
        onChange={(e) => setMensaje(e.target.value)}
        placeholder="Escribe tu mensaje..."
      />
      <button onClick={crearPublicacion}>
        Crear Publicación
      </button>
      <button onClick={actualizarContador}>
        Incrementar Visitas
      </button>
    </div>
  );
}

📁 Cloud Storage

Cloud Storage te permite almacenar y servir archivos como imágenes, videos y documentos.

Hooks principales para Storage:

  • useStorage() - Obtiene la instancia de Storage
  • useStorageDownloadURL() - Obtiene la URL de descarga de un archivo
  • useStorageTask() - Monitorea el progreso de una subida

Ejemplo: Subir una imagen

import React, { useState } from 'react';
import { useStorage, useStorageTask } from 'reactfire';
import { ref, uploadBytesResumable } from 'firebase/storage';

function SubirImagen() {
  const storage = useStorage();
  const [uploadTask, setUploadTask] = useState(null);
  const [imagenRef, setImagenRef] = useState(null);

  const handleFileChange = (event) => {
    const archivo = event.target.files[0];
    if (archivo) {
      // Crear referencia al archivo
      const nuevaRef = ref(storage, `imagenes/${archivo.name}`);
      setImagenRef(nuevaRef);

      // Crear tarea de subida
      const tarea = uploadBytesResumable(nuevaRef, archivo);
      
      // Configurar eventos
      tarea.then(() => {
        console.log('¡Subida completada!');
        setUploadTask(null);
      }).catch((error) => {
        console.error('Error en la subida:', error);
        setUploadTask(null);
      });

      setUploadTask(tarea);
    }
  };

  return (
    <div>
      <h3>Subir Imagen</h3>
      <input 
        type="file" 
        accept="image/*" 
        onChange={handleFileChange} 
      />
      
      {uploadTask && (
        <ProgresoSubida 
          uploadTask={uploadTask} 
          storageRef={imagenRef} 
        />
      )}
    </div>
  );
}

Ejemplo: Monitorear progreso de subida

import React from 'react';
import { useStorageTask } from 'reactfire';

function ProgresoSubida({ uploadTask, storageRef }) {
  const { data: progreso } = useStorageTask(uploadTask, storageRef);

  const { bytesTransferred, totalBytes } = progreso;
  const porcentaje = Math.round(100 * (bytesTransferred / totalBytes));

  return (
    <div>
      <div className="barra-progreso">
        <div 
          className="progreso" 
          style={{ width: `${porcentaje}%` }}
        />
      </div>
      <p>Progreso: {porcentaje}% ({bytesTransferred} / {totalBytes} bytes)</p>
    </div>
  );
}

Ejemplo: Mostrar imagen desde Storage

import React, { Suspense } from 'react';
import { useStorage, useStorageDownloadURL } from 'reactfire';
import { ref } from 'firebase/storage';

function MostrarImagen({ rutaImagen }) {
  const storage = useStorage();
  const imagenRef = ref(storage, rutaImagen);
  
  // Obtener URL de descarga
  const { data: urlDescarga } = useStorageDownloadURL(imagenRef);

  return (
    <div>
      <h4>Mi Imagen:</h4>
      <img 
        src={urlDescarga} 
        alt="Imagen desde Firebase Storage"
        style={{ maxWidth: '300px', height: 'auto' }}
      />
    </div>
  );
}

function GaleriaImagenes() {
  return (
    <div>
      <h3>Galería</h3>
      <Suspense fallback={<div>Cargando imagen...</div>}>
        <MostrarImagen rutaImagen="imagenes/foto1.jpg" />
      </Suspense>
      
      <Suspense fallback={<div>Cargando imagen...</div>}>
        <MostrarImagen rutaImagen="imagenes/foto2.jpg" />
      </Suspense>
    </div>
  );
}

💡 Ejemplos Prácticos

Aquí tienes algunos ejemplos completos que combinan diferentes servicios de Firebase:

Ejemplo: Chat en Tiempo Real

import React, { useState, Suspense } from 'react';
import { 
  useFirestore, 
  useFirestoreCollectionData, 
  useAuth, 
  useUser 
} from 'reactfire';
import { 
  collection, 
  addDoc, 
  query, 
  orderBy, 
  limit,
  serverTimestamp 
} from 'firebase/firestore';

function ChatApp() {
  return (
    <div className="chat-app">
      <h2>💬 Chat en Tiempo Real</h2>
      <Suspense fallback={<div>Cargando chat...</div>}>
        <ListaMensajes />
        <FormularioMensaje />
      </Suspense>
    </div>
  );
}

function ListaMensajes() {
  const firestore = useFirestore();
  
  const mensajesQuery = query(
    collection(firestore, 'mensajes'),
    orderBy('timestamp', 'desc'),
    limit(50)
  );
  
  const { data: mensajes } = useFirestoreCollectionData(mensajesQuery, {
    idField: 'id'
  });

  return (
    <div className="lista-mensajes">
      {mensajes.reverse().map(mensaje => (
        <div key={mensaje.id} className="mensaje">
          <strong>{mensaje.autor}:</strong> {mensaje.texto}
        </div>
      ))}
    </div>
  );
}

function FormularioMensaje() {
  const [texto, setTexto] = useState('');
  const firestore = useFirestore();
  const { data: user } = useUser();

  const enviarMensaje = async (e) => {
    e.preventDefault();
    if (texto.trim()) {
      await addDoc(collection(firestore, 'mensajes'), {
        texto: texto,
        autor: user.displayName || 'Anónimo',
        authorId: user.uid,
        timestamp: serverTimestamp()
      });
      setTexto('');
    }
  };

  return (
    <form onSubmit={enviarMensaje} className="formulario-mensaje">
      <input
        value={texto}
        onChange={(e) => setTexto(e.target.value)}
        placeholder="Escribe tu mensaje..."
        maxLength={200}
      />
      <button type="submit">Enviar</button>
    </form>
  );
}

Ejemplo: Lista de Tareas Colaborativa

import React, { useState, Suspense } from 'react';
import { 
  useFirestore, 
  useFirestoreCollectionData,
  useUser 
} from 'reactfire';
import { 
  collection, 
  addDoc, 
  updateDoc, 
  deleteDoc,
  doc,
  query,
  where 
} from 'firebase/firestore';

function AppTareas() {
  return (
    <div className="app-tareas">
      <h2>📝 Lista de Tareas Colaborativa</h2>
      <Suspense fallback={<div>Cargando tareas...</div>}>
        <FormularioTarea />
        <ListaTareas />
      </Suspense>
    </div>
  );
}

function FormularioTarea() {
  const [titulo, setTitulo] = useState('');
  const firestore = useFirestore();
  const { data: user } = useUser();

  const crearTarea = async (e) => {
    e.preventDefault();
    if (titulo.trim()) {
      await addDoc(collection(firestore, 'tareas'), {
        titulo,
        completada: false,
        autorId: user.uid,
        autorNombre: user.displayName,
        fechaCreacion: new Date()
      });
      setTitulo('');
    }
  };

  return (
    <form onSubmit={crearTarea}>
      <input
        value={titulo}
        onChange={(e) => setTitulo(e.target.value)}
        placeholder="Nueva tarea..."
      />
      <button type="submit">Agregar</button>
    </form>
  );
}

function ListaTareas() {
  const firestore = useFirestore();
  
  const tareasQuery = query(
    collection(firestore, 'tareas')
  );
  
  const { data: tareas } = useFirestoreCollectionData(tareasQuery, {
    idField: 'id'
  });

  const toggleCompletada = async (tareaId, completada) => {
    const tareaRef = doc(firestore, 'tareas', tareaId);
    await updateDoc(tareaRef, { completada: !completada });
  };

  const eliminarTarea = async (tareaId) => {
    const tareaRef = doc(firestore, 'tareas', tareaId);
    await deleteDoc(tareaRef);
  };

  return (
    <div className="lista-tareas">
      {tareas.map(tarea => (
        <div key={tarea.id} className={`tarea ${tarea.completada ? 'completada' : ''}`}>
          <input
            type="checkbox"
            checked={tarea.completada}
            onChange={() => toggleCompletada(tarea.id, tarea.completada)}
          />
          <span>{tarea.titulo}</span>
          <small>por {tarea.autorNombre}</small>
          <button onClick={() => eliminarTarea(tarea.id)}>
            🗑️
          </button>
        </div>
      ))}
    </div>
  );
}

💡 Consejos y Mejores Prácticas

🎣 Usa Suspense

Siempre envuelve componentes que usan hooks de datos con Suspense para mostrar estados de carga elegantes.

🔐 Reglas de Seguridad

Nunca olvides configurar las reglas de seguridad en Firebase para proteger tus datos.

📱 Datos en Tiempo Real

Los hooks como useFirestoreDocData se actualizan automáticamente cuando cambian los datos en Firebase.

⚡ Optimización

Usa useFirestoreDocDataOnce cuando solo necesites leer datos una vez, no en tiempo real.

🔗 Enlaces Útiles