Curso de React | 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.

Ejercicios

Lista de tareas

1. 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 ello utilizaremos 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;
}
Curso de React | Firebase (versión 9). Autentificación y tiempo real 1

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

3. En la pantalla de tareas podremos crear nuevas tareas y ver las existentes:

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

Foro

En la carpeta services, crearemos dos ficheros: hilos y users. La entidad hilos tendrá en su interior un array de comentarios.

Página de crear un hilo.

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

Página de ver hilos.

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

Página de ver la información de un hilo.

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

Este ejercicio se puede ampliar de muchas formas:

  • Indicando el nombre de la persona que ha escrito el comentario.
  • Indicando la fecha de la publicación.
  • Añadiendo una capa de diseño.

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 8

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 9

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 10

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 11
Curso de React | Firebase (versión 9). Autentificación y tiempo real 12

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.

import { auth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut} 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) {
        return err.message;
    }
}

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

Luego, si queremos saber si el usuario verificó su cuenta de email:

  useEffect(() => {
    onAuthStateChanged(auth, user => {
      console.log('oooooooooooooo', user.emailVerified);
    })
  }, [])

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;
    });
}

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/>

A tener en cuenta en la autentificación

Hacer todo el proceso de autentificación y pulirlo hasta la perfección puede llevar meses. La validacción de la cuenta de correo haciendo click en el correo electrónico que nos llega, la recuperación de la contraseña, las pantallas vinculadas al login y registro. Firebase cuenta con métodos para realizar todo el proceso, sin embargo serás tu quien deberá implementarlas para realizar todo el sistema de autentificación y registro.

Para ahorrarnos todo este proceso, el servicio auth0 ya tiene todas las pantallas funciones integradas y sólo tendremos que añadirlo a nuestro desarrollo.

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.

export const onTextUpdated = (callback) => onSnapshot(colRef, (docs) => {
    callback(getArrayFromCollection(docs));
});

Código en la vista:

  useEffect(() => {
    getMessages();
    onTextUpdated((messages) => {
      setItems(messages);
    });
  }, []);

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.