Curso de React | 24. Firebase (versión 9). Autentificación y tiempo real

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

Como hacer una autentificación

Autentificación con mail y password

Con el método setDoc podremos guardar en la colección «users» un documento cuya especificándole expresamente cual será su id. En este caso hemos seleccionado que la id del documento sea la id del usuario de Firebase.

/services/auth.js

import { auth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, doc, setDoc} from "./firebase";

export const signUp = async (email, password) => {
    try {
        const userCredential = await createUserWithEmailAndPassword(auth, email, password);
        // sendEmailVerification(userCredential.user);
        const user = userCredential.user;
        const docRef = doc(db, 'users', user.uid);
        await setDoc(docRef, {});
        return user.uid;
    } catch (err) {
        return err.message;
    }
}

export const signIn = async (email, password) => {
    try {
        const result = await signInWithEmailAndPassword(auth, email, password);
        return result.user.uid;
    } catch (err) {
        console.log('Ha habido un error:', err);
        return err.message;
    }
}

export const getCurrentUserId = async () => await auth.currentUser?.uid;
export const logout = async () => await signOut(auth);

Hacer una acción cuando el usuario se loguee/desloguee

/src/App.js

import { onAuthStateChanged } from 'firebase/auth';
...

const {user, setUser} = useUserContext();

useEffect(() => {
    onAuthStateChanged(auth, user => {
        if (user) {
            console.log('user', user, ' userId:', user.uid);
            setUser(user);
        } else {
            console.log("No user logged");
            setUser(null);
        }
    });
}, []);

return user ? <Router/> : <Login/>

Ejercicio

Hacer una aplicación que use la autentificación con mail y password para que cuando el usuario acceda se muestre un componente (<Content/>) y cuando esté sin loguear se muestre otro (<Login/>).

Pasos para lograrlo:

1. Creamos un componente <Home/> que tendrá el siguiente formulario y lo insertamos en App:

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

export default App;
Curso de React | Firebase (versión 9). Autentificación y tiempo real 1

2. Al pulsar el botón de SignUp, debemos llamar al método correspondiente que insertará al usuario en Firebase. Debemos comprobar en el menú de Authentification que el usuario se ha insertado correctamente.

3. Implementamos el código de onAuthStateChanged para que cuando el usuario esté logueado se cargue el componente <Content/> y cuando no lo esté se cargue <Login/>

Para conseguirlo, utilizarás Context API para guardar los datos del usuario logueado, y en el componente APP cargarás un componente u otro utilizando este código:

/src/App.js

return {user ? <Content/> : <Login/>}

Autentificación con cuenta de Google

Este comando mostrará un popup a través del cual podremos autentificarnos.

import { GoogleAuthProvider, signInWithPopup} from "firebase/auth";

export const loginWithGoogle = () => {
    const provider = new GoogleAuthProvider();
    return signInWithPopup(auth, provider).then(result => {
        return result.user;
    });
}

Ejercicio

Hacer una aplicación que use la autentificación con mail y password para que cuando el usuario acceda se muestre un componente (<Content/>) y cuando esté sin loguear se muestre otro (<Login/>).

Para conseguirlo, utilizarás Context API para guardar los datos del usuario logueado, y en el componente APP cargarás un componente u otro utilizando este código:

return {user ? <Content/> : <Login/>}

Ejercicios

Lista de tareas

Cuando el usuario se autentifique accederá a una página en la que podrá añadir y consultar tareas.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 2

En la base de datos de firebase tendremos una entidad llamada user que a su vez tendrá una colección de tasks.

Para recuperar las tareas del usuario logueado, utilizaremos un método similar a este:

export const getTasksByUserId = async (userId) => {
    const colRef = collection(db, 'users', userId, 'tasks');
    const result = await getDocs(colRef);
    return getArrayFromCollection(result);
}

Para añadir una tarea al usuario, usaremos una referencia a la colección similar a esta:

const colRef = collection(db, 'users', idUser, 'tasks');

Los siguientes pasos son opcionales.

4. Añade la posibilidad de eliminar tareas existentes.

5. Añade la posiblidad de actualizar tareas existentes. Para ello, tendremos un botón de actualizar que mostrará el texto de la tarea dentro de un input. Pulsando el botón de actualizar realmente, podremos modificar la tarea en la base de datos.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 3
Curso de React | Firebase (versión 9). Autentificación y tiempo real 4

Mensajería

En la Home de esta aplicación de mensajería introduciremos nuestro nombre. Si ese nombre no existía en la base de datos, lo introduciremos. Para lograr esto, podemos utilizar el siguiente código:

export const access = async (name) => {
    const colRef = collection(db, collectionName);
    const result = await getDocs(query(colRef, where('name', '==', name)));
    if (result.size === 0) {
        const a = await addDoc(colRef, { name });
        return a.id;
    }
    return result.docs[0].id;
}

Tanto si el nombre no existía como si existía, recuperaremos su id y lo salvaremos en la memoria global utilizando ContextApi.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 5

Para gestionar las peticiones tendremos dos ficheros independientes. Uno para gestionar las peticiones referentes a los usuarios y otro para gestionar las peticiones referentes a los mensajes.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 6

En la pantalla de escribir mensaje escibiremos el mensaje que queremos enviar y marcaremos la check correspondiente a cada uno de los destinatarios.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 7

En la pantalla de ver mensajes podremos ver los mensajes que nos han enviado.

En la página de write mesage tendremos un estado que contendrá un array con las id’s correspondientes a los usuarios marcados. Para alimentar ese array en función de las checkboxes marcadas, usaremos el siguiente código:

// Cada vez que una checkbox cambia, se actualiza el array de usuarios seleccionados
const onChange = (userId) => {
    if (checkedUsersIds.includes(userId)) {
        setCheckedUsersIds(checkedUsersIds.filter(id => userId != id))
    } else {
        setCheckedUsersIds([...checkedUsersIds, userId]);
    }
}

Cada mensaje tendrá tres propiedades: idRemitente, idUsuario y msg:

Curso de React | Firebase (versión 9). Autentificación y tiempo real 8
Curso de React | Firebase (versión 9). Autentificación y tiempo real 9
Ver ejemplo

Tiempo real en Firebase

La siguiente función envía a la vista un array con los datos de la colección actualizada.

ItemsCollection es la colección cuyos cambios estamos escuchando.

// Ejemplo de como escucho cuando se actualiza una colección
export const onStudentsUpdated = (callback) => 
  onSnapshot(collection(db, "students"), (docs) => {
    console.log("Colección actualizada");
    callback(docs);
});

// Ejemplo de como escucho cuando se actualiza un documento
export const onRoomUpdated = (roomId, callback) =>
  onSnapshot(doc(db, "rooms", roomId), (doc) => {
    console.log("Documento actualizado");
    callback(doc);
  });

Código en la vista:

  useEffect(() => {
    getMessages();
    onRoomUpdated ((roomData) => {
      // Hago cosas con RoomData
    });
  }, []);

Ejercicio Juego en tiempo real

Utilizando las capacidades de tiempo real de firebase, vamos a hacer el videojuego del tres en raya. Los jugadores se conectarán una sala de espera. En ella, aparecerá la ID de tu sala y un cuadro de texto en el que podrás poner la id de la sala del compañero.

  • El anfitrión es el que le da su ID de sala al invitado..
  • El invitado es el que escribe la ID de sala que le ha dado el anfitrión.

Intentaremos que todo el código de este componente esté en una carpeta. De esta forma, si queremos reutilizarlo en otro proyecto, nos será fácil.

Pasos para desarrollar el ejercicio.

1. Creamos la interfaz visual.

Curso de React | Firebase (versión 9). Autentificación y tiempo real 10

2. Nada más llegar a la página anterior, en el caso de que no exista una sala con la id seleccionada, crearemos documento en la colección rooms cuya id será la id de la sala. Para ello, llamaremos a un método createRoom(roomId) que llamaremos dentro del useEffect del RoomProvider.

3. Cuando el invitado pulse sobre el botón de acceder, modificaremos el documento de la sala y le añadiremos la propiedad guestIsReady:true.

4. El siguiente código dentro del RoomProvider actualiza la información de la sala, que es una variable de contexto:

  useEffect(() => {
    onRoomUpdated(roomId, (roomDataRecovered) => {
      if (roomDataRecovered?.guestIsReady === true) {
        setRoomData(roomDataRecovered);
      }
    });
  }, [roomId]);

Te muestro como quedaría el RoomProvider:

import { createContext, useState, useContext, useEffect } from "react";
import { createRoom, onRoomUpdated } from "../services/room";

const AppContext = createContext();
export const useRoomContext = () => useContext(AppContext);
const rId = Math.random().toString(36).substring(7);

const RoomProvider = ({ children }) => {
  const [roomId, setRoomId] = useState(rId);
  const [isHost, setIsHost] = useState(true);
  const [roomData, setRoomData] = useState();

  useEffect(() => {
    createRoom(roomId).then(() => {
      console.log("room created", roomId);
    });
  }, []);

  useEffect(() => {
    onRoomUpdated(roomId, (roomDataRecovered) => {
      if (roomDataRecovered?.guestIsReady === true) {
        setRoomData(roomDataRecovered);
      }
    });
  }, [roomId]);

  return (
    <AppContext.Provider
      value={{
        roomId,
        setRoomId,
        isHost,
        setIsHost,
        roomData,
        setRoomData,
      }}
    >
      {children}
    </AppContext.Provider>
  );
};

export default RoomProvider;

5. En App cargamos el componente Game, que contiene el juego, en el caso de que la propiedad guestIsReady de la sala sea true:

const App = () => {
  const { roomData } = useRoomContext();
  return <>{roomData?.guestIsReady === true ? <Game /> : <Lobby />}</>;
}

export default App;
Descargar código fuente

Guardar medios en Firebase

En el área de administración de firebase, iremos a menú StorageRules → Seteamos el valor a true:

rules_version = '2';
service firebase.storage {
   match /b/{bucket}/o {
      match /{allPaths=**} {
         allow read, write: if true;
      }
   }
}
firebase.js
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
    ...
    storageBucket: import.meta.env.REACT_APP_PROJECT_ID + ".appspot.com",
};

export const storage = getStorage(firebaseApp);
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import {storage} from './firebase';

export const uploadImage = async (file, uid) => {
    const storageRef = ref(storage, `/files/${uid}/${file.name}`);
    const uploadTask = uploadBytes(storageRef, file);
    uploadTask.then(async (data) => {
        const url = await getDownloadURL(data.ref);
        const colRef = collection(db, "students");
        await updateDoc(doc(colRef, uid), { uploadedPicture: url });
    })
}

App.jsx

<input type='file' onChange={(e) => setFile(e.target.files[0])} />

Relación muchos a muchos

Suponemos que nuestra aplicación utiliza una relación muchos a muchos. Por ejemplo, imagina que tenemos dos colecciones: usuarios y eventos. Un usuario puede tener muchos eventos y un evento puede tener muchos usuarios. Para gestionar esa relación crearemos una tabla intermedia en la que cada documento tendrá dos propiedades:

  • La id del usuario.
  • La id del evento.

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.