Autenticación con Firebase y React JS 🔑
Tags: react, javascript, spanish, firebase
Published: 2022-11-11 05:52:42

En esta ocasión te mostrare como usar el servicio de Firebase para realizar una autenticación en tu aplicación de React JS. 

Cualquier tipo de **feedback** es bienvenido, gracias y espero disfrutes el articulo.🤗

>**⚠️ Nota**: Es necesario que cuentes con conocimientos básicos en React JS y hooks y TypeScript.

&nbsp; 

## Tabla de contenido
> 📌 [Tecnologías a utilizar.](#1)  
> 📌 [Creando el proyecto.](#2)  
> 📌 [Primeros pasos.](#3)  
> 📌 [Creando los diseños de autenticación.](#4)  
>> 📌 [Diseño del inicio de sesión.](#5)  
>> 📌 [Diseño del registro.](#6)  
>
> 📌 [Manejando los estados de los formularios.](#7)  
> 📌 [Configurar Firebase.](#8)  
>> 📌 [Creando el proyecto.](#9)  
>> 📌 [Creando la app.](#10)  
>> 📌 [Configurando la autenticación.](#11)  
>
> 📌 [Configurando Firebase en nuestra aplicación de React.](#12)  
> 📌 [Creando las funciones para la autenticación.](#13)  
>> 📌 [1 - Creando la función para autenticarse por Google.](#14)  
>> 📌 [2 - Creando la función para autenticarse por credenciales.](#15)  
>> 📌 [3 - Creando la función para observar los cambios en el estado de autenticación del usuario.](#16)  
>> 📌 [4 - Creando la función para cerrar sesión.](#17)  
>  
> 📌 [Creando un contexto de nuestra aplicación.](#18)  
> 📌 [Usando nuestro contexto.](#19)  
> 📌 [Conclusión.](#20)  
>> 📌 [Código fuente.](#21)  

&nbsp; 


## 🔥 Tecnologías a utilizar. <a id="1"></a>

- ▶️ React JS (version 18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ Firebase (version 9.10)
- ▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post) 

## 🔥 Creando el proyecto. <a id="2"></a>

Al proyecto le colocaremos el nombre de: **`auth-firebase-react`** (opcional, tu le puedes poner el nombre que gustes).
```bash
npm init vite@latest
```
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.  
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

```bash
cd auth-firebase-react
```
Luego instalamos las dependencias.
```bash
npm install
```
Después abrimos el proyecto en un editor de código (en mi caso VS code).
```bash
code .
```

## 🔥 Primeros pasos. <a id="3"></a>
Primero vamos al archivo `src/App.tsx` y borramos todo, vamos a crear un nuevo componente que muestre un simple titulo.

```tsx
const App = () => {
  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
    </main>
  )
}
export default App
```
Se vería asi, si es que usas los mismos estilos que este proyecto. 👀

![title](https://res.cloudinary.com/dnxchppfm/image/upload/v1663804708/firebase-auth-react/01-app_cq7rcb.png)

## 🔥 Creando los diseños de autenticación. <a id="4"></a>
En esta aplicación no manejaremos rutas, solamente nos enfocamos en la autenticación. Asi que los diseños de login y register pueden ponerlos en vistas separadas si gustan, en mi caso solo serán componentes. 

Lo hago de esta manera para explicarlo de la manera más simple.

### 🔥 Diseño del inicio de sesión. <a id="5"></a>
Creamos la carpeta `src/components` y dentro creamos el archivo **Login.tsx**

El inicio de sesión constara de 2 inputs:
- **Email**. 
- **Password**. 

Los input, deben de tener contener el atributo **name** identificando cada input.


También tendrá 2 botones:
- **Inicio de sesión normal**. 
- **Inicio de sesión con Google**. 

Cada botón debe tener su propiedad **type** esto es para que el botón de Google no haga el posteo del formulario, solamente el primer botón debe hacer eso.

```tsx
export const Login = () => {

    return (
        <div className="container-auth">
            <h2>Login</h2>

            <form>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                />

                <div className="container-buttons">
                    <button type="submit">Log In</button>
                    <button type="button"> Google </button>
                </div>
            </form>
        </div>
    )
}
```

### 🔥 Diseño del registro. <a id="6"></a>

El diseño del registro sera igual al del inicio de sesión, con los mismos inputs pero solamente tiene un botón

```tsx
export const Register = () => {

    return (
        <div className="container-auth">
            <h2>Create an account</h2>

            <form>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                />
                <div className="container-buttons">
                    <button type="submit">Sign up</button>
                </div>
            </form>
        </div>
    )
}
```

Ahora creamos un archivo barril, (**index.ts**) en la carpeta `src/components` para poder exportar nuestros componentes e importarlos en otro lugar de una manera mas ordenada.

```tsx
export * from './Login'
export * from './Register'
```

Una vez tengamos los dos diseños y el archivo barril, vamos a `src/App.tsx` y los agregamos:

```tsx
import { Login, Register } from "./components"

const App = () => {

  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
      <section>
        <Login />
        <Register />
      </section>
    </main>
  )
}
export default App
```

El resultado del diseño seria el siguiente:
![auth](https://res.cloudinary.com/dnxchppfm/image/upload/v1663804711/firebase-auth-react/02-app_ag2vuk.png)

## 🔥 Manejando los estados de los formularios. <a id="7"></a>

Para manejar cada formulario, vamos a implementar un custom hook, lo llamaremos **useForm.tsx** y estará dentro de la carpeta `src/hooks`.

Creamos una función que recibe como parámetro un objeto que contenga el estado inicial del formulario y este tendrá un tipo genérico, esto para que sea un poco mas reutilizable (`por si quieren agregar mas campos a los formularios`) el hook aunque en este caso no es necesario ya que tenemos los mismos campos en ambos formularios

```ts
interface Props<T> {
    initialState: T
}

export const useForm = <T>({ initialState }: Props<T>) => {}
```

Después, usaremos el estado para almacenar los valores del formulario. y con la función **handleChange** vamos a poder manejar los cambios del input y almacenar sus valores.

La función ***handleChange*** mandamos una propiedad computada a la función **setForm**  y es por eso que es necesario el **atributo name en el input**, para identificarlo y obtener su valor.

Finalmente retornamos, mediante el operador rest esparcimos los valores del formulario, luego el formulario y finalmente la función handleChange.

```ts
import { useState } from 'react';

type eventInput = React.ChangeEvent<HTMLInputElement>;

interface Props<T> {
    initialState: T
}

export const useForm = <T>({ initialState }: Props<T>) => {

    const [form, setForm] = useState<T>(initialState)

    const handleChange = (e: eventInput) => {
        setForm(prev => ({
            ...prev,
            [e.target.name]: e.target.value
        }))
    }

    return { ...form, form, handleChange }
}
```

Ahora usamos el hook en `src/components/Login.tsx`:

Llamamos al hook **useForm** le mandamos un objeto que sera el estado inicial del formulario, en este caso tendremos dos propiedades que es el **email** y **pass**, que hacen referencia a los inputs que tenemos en el HTML. Sus valores por defecto son string vacíos.

Desestructuramos las propiedades del formulario y la función handleChange.

```ts
const { handleChange, pass, email } = useForm({
    initialState: {
        email: '',
        pass: ''
    }
})
```

En los inputs colocamos el atributo value con su correspondiente valor y el atributo onChange mandando la función handleChange para manejar el estado del input.

```tsx
<input
    name="email"
    type="email"
    placeholder="E-mail"
    onChange={handleChange}
    value={email}
/>
<input
    name="pass"
    type="password"
    placeholder="Password"
    onChange={handleChange}
    value={pass}
/>
```

Finalmente, haremos una función llamada **handleSubmit** que recibe el evento del formulario, y esta función por el momento solo previene el comportamiento del formulario por defecto. 

La función **handleSubmit** se la pasamos al atributo **onSubmit** de la etiqueta **form**.

```ts
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
}
```

Asi quedaría por el momento el **Login.tsx**

```tsx
import { useForm } from '../hooks/useForm';

export const Login = () => {

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: '',
            pass: ''
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
    }

    return (
        <div className="container-auth">
            <h2>Login</h2>

            <form onSubmit={handleSubmit}>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                    onChange={handleChange}
                    value={email}
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                    onChange={handleChange}
                    value={pass}
                />

                <div className="container-buttons">
                    <button type="submit">Log In</button>
                    <button type="button"> Google </button>
                </div>
            </form>
        </div>
    )
}
```

Asi como hicimos el **Login.tsx** es exactamente igual en el archivo **Register.tsx**

```tsx
import { useForm } from "../hooks/useForm"

export const Register = () => {

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: '',
            pass: ''
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
    }

    return (
        <div className="container-auth">
            <h2>Create an account</h2>

            <form onSubmit={handleSubmit}>
                <input
                    name="email"
                    type="email"
                    placeholder="E-mail"
                    onChange={handleChange}
                    value={email}
                />
                <input
                    name="pass"
                    type="password"
                    placeholder="Password"
                    onChange={handleChange}
                    value={pass}
                />
                <div className="container-buttons">
                    <button type="submit">Sign up</button>
                </div>
            </form>
        </div>
    )
}
```

>**⚠️ Nota**: Incluso pueden crear un único componente reutilizable, ya que los formularios son casi exactamente iguales solo se diferencian por los botones y la acción de **handleSubmit** ya que esta función hará algo diferente dependiendo el formulario. 

Ya tenemos el diseño y funcionalidad de los formularios, seguimos con la creación de nuestra app en Firebase.

## 🔥 Configurar Firebase. <a id="8"></a>
Ahora nos toca configurar la aplicación en firebase para poder usar su servicio de autenticación.

### 🔥 Creando el proyecto. <a id="9"></a>

1 - Vamos a la [consola de firebase](https://console.firebase.google.com/), iniciamos sesión con alguna cuenta de correo de Gmail.

2 - Si es la primera vez que usan Firebase, les aparecerá un mensaje y botón de **crear proyecto** el cual deben de presionar.

3 - Colocar el nuevo nombre al proyecto. En mi caso le puse **auth-firebase-react**. 

Y al final hay un **botón de continuar** que debes presionar.

![new name protect](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815969/firebase-auth-react/01_sqz1zr.png) 

4 - Esperar a que tu proyecto termine de crearse y después dar click en **continuar**

![loading ](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815969/firebase-auth-react/02_uxh4wq.png)

Una vez en continuar te va a mandar a un nuevo panel.

### 🔥 Creando el la app. <a id="10"></a>

1 - En el nuevo panel, tienes que identificar estos botones. **Presiona el botón de web para crear una app** (el tercer botón con fondo blanco).

![buttons](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815966/firebase-auth-react/03_wckfrq.png)

2 - Coloca el nombre a tu app y dale click en **Registrar app**

![new app](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815967/firebase-auth-react/04_hgmplp.png)


3 - Una vez registrada la app, nos darán nuestras credenciales, las cuales tenemos que guardar porque las vamos a usar después. <a id="credentials"></a>

![credentials](https://res.cloudinary.com/dnxchppfm/image/upload/v1663816008/firebase-auth-react/05_db8kkb.png)

### 🔥 Configurando la autenticación. <a id="11"></a>

1 - Ahora, tenemos que regresar al panel, en el menu del lado derecho hay una opción que dice **compilación** y dentro esta la opción de **autenticación** y tienes que darle click, para que te lleve a otra pantalla. 

Y tienes que dar click al botón de **Comenzar**

![auth page](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815969/firebase-auth-react/06_sjq7bz.png)

2 - Después se te mostraran diversos proveedores, de los cuales vamos a seleccionar el de **correo electrónico/contraseña** y **Google** (puedes elegir los que quieras, aunque probablemente tendrás que hacer mas configuración).

- Cuando elijas el de **correo electrónico/contraseña** solamente le das en **Habilitar** y al final viene un botón de **guardar** que debes presionar una vez que termines de hacer alguna modificación en el proveedor.

- Cuando elijas el de **Google** harás lo mismo que el anterior, y también tendrás que seleccionar tu correo electrónico.

![providers](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815969/firebase-auth-react/07_phqeql.png)

3 - Una vez habilitados los proveedores, tendrá que aparecerte en la pestaña **Sign-in method** de la siguiente forma.

![providers added](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815986/firebase-auth-react/08_ognn26.png)

4 - En la pestaña Users puedes ver todos los usuarios que se registren en tu aplicación.

![users](https://res.cloudinary.com/dnxchppfm/image/upload/v1663815969/firebase-auth-react/09_fenl2f.png)


## 🔥 Configurando Firebase en nuestra aplicación de React. <a id="12"></a>

De vuelta a nuestra app de React, vamos a instalar Firebase.

```
npm install firebase
```

creamos una nueva carpeta `src/firebase` y dentro un archivo llamado **config.ts** y pegamos toda la configuración que nos dieron en la [sección anterior](#credentials) 


En mi caso, yo coloque los valores de cada propiedad en una variable de entorno, solamente creando en la raíz del proyecto un archivo .env.

Cada variable debe empezar con la palabra **VITE_** para que funcionen.
```bash
VITE_APIKEY=1231231465
# more vars
```

Y para llamar a una variable de entorno tenemos que user el **import.meta.env['Nombre de la variable']**

>Nota: también debes notar que cambie el nombre de la variable **app** por **FirebaseApp**
```ts
import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: import.meta.env.VITE_APIKEY,
    authDomain: import.meta.env.VITE_AUTHDOMAIN,
    projectId: import.meta.env.VITE_PROJECTID,
    storageBucket: import.meta.env.VITE_STORAGEBUCKET,
    messagingSenderId: import.meta.env.VITE_MESSAGINGSENDERID,
    appId: import.meta.env.VITE_APPID,
};

// Initialize Firebase
export const FirebaseApp = initializeApp(firebaseConfig)
```

Ahora para utilizar el servicio de autenticación de Firebase, usamos el método **getAuth** y tenemos que obtenerlo de **'firebase/auth'**, después le mandamos la inicialización de nuestra app, o sea la constante **FirebaseApp**

```ts
import { getAuth } from 'firebase/auth'

export const FirebaseApp = initializeApp(firebaseConfig)
export const FirebaseAuth = getAuth(FirebaseApp)
```

La configuración quedaría de esta manera 👀:

```ts
import { initializeApp } from "firebase/app";

const firebaseConfig = {
    apiKey: import.meta.env.VITE_APIKEY,
    authDomain: import.meta.env.VITE_AUTHDOMAIN,
    projectId: import.meta.env.VITE_PROJECTID,
    storageBucket: import.meta.env.VITE_STORAGEBUCKET,
    messagingSenderId: import.meta.env.VITE_MESSAGINGSENDERID,
    appId: import.meta.env.VITE_APPID,
};

export const FirebaseApp = initializeApp(firebaseConfig)
export const FirebaseAuth = getAuth(FirebaseApp)
```


## 🔥 Creando las funciones para la autenticación. <a id="13"></a>
Ahora vamos a crear un nuevo archivo dentro de `src/firebase` llamado **services.ts**

>Nota: todas las funciones de firebase que vamos a usar vienen de **firebase/auth**

### 🔥 1 - Creando la función para autenticarse por Google. <a id="14"></a>

Primero debemos crear una nueva instancia del proveedor que hayamos escogido, en este caso Google.

Luego creamos un método asíncrono, y dentro un try/catch porque ya sea que el usuario se equivoque o algo salga mal.

Mediante el método **signInWithPopup**, tendemos que mandarle nuestra instancia de **FirebaseAuth**, que ya habíamos creado en la sección anterior, y la **instancia del proveedor**.

Si todo sale correcto, de la propiedad user de la variable resultado, te dará varia información como lo puedes ver en la desestructuración, pero solo vamos a usar el **uid** por eso lo retornamos.

Y en el catch, **de hecho en todos los catch de este archivo, solo vamos a mandar una alerta con el mensaje que nos proporciona Firebase**

```ts
import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'
import { FirebaseAuth } from './config'

const googleProvider = new GoogleAuthProvider()

export const singInWithGoogle = async () => {
    try {
        const result = await signInWithPopup(FirebaseAuth, googleProvider)

        const { displayName, email, photoURL, uid } = result.user

        return uid

    } catch (e) {
        alert((e as Error).message)
    }
}
```

### 🔥 2 - Creando la función para autenticarse por credenciales. <a id="15"></a>

La función para login y register usando credenciales son las mismas solo se diferencian por el método.   
Ambas reciben un objeto que contiene el **email** y **password** y si todo sale bien, retornan el **uid** (estas funciones también devuelven lo mismo que el de autenticarse con google, como displayName, photoURL, etc.)

Tanto la función de **createUserWithEmailAndPassword** y **signInWithEmailAndPassword** reciben la instancia de FirebaseAuth, y un email y password.

- **createUserWithEmailAndPassword**, crea el usuario en Firebase.
- **signInWithEmailAndPassword**, verifica si existe el usuario en Firebase.


```ts
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth'
import { FirebaseAuth } from './config'

interface PropsRegister { email: string, password: string }

export const signInWithCredentials = async ({ email, password }: PropsRegister) => {

    try {
        const resp = await createUserWithEmailAndPassword(FirebaseAuth, email, password);
        return resp.user.uid

    } catch (e) {
        alert((e as Error).message)
    }

}

export const loginWithCredentials = async ({ email, password }: PropsRegister) => {

    try {
        const resp = await signInWithEmailAndPassword(FirebaseAuth, email, password);
        return resp.user.uid

    } catch (e) {
        alert((e as Error).message)
    }
}
```

### 🔥 3 - Creando la función para observar los cambios en el estado de autenticación del usuario. <a id="16"></a>

**¿Por que queremos observar el estado de autenticación del usuario?**
Bueno, supongamos que iniciamos sesión correctamente, todo sale muy  bien, estamos dentro de la application ya autenticados 🤩. Pero upss! refrescamos el navegador y se nos pierde la sesión, y tenemos que volver a iniciar sesión 😥.

Asi que como resolvemos este problema, pues observando el estado de autenticación del usuario.

Para ello necesitamos un par de cosas.
Primero crear una función, que va a recibir como parámetro un callback, o sea una función, dicha función nos ayudara a establecer el usuario autenticado o no autenticado. 

Puedes notar en el código que usaremos un setter de useState y que ademas usaremos el Context API.El tipado les fallara porque aun no tenemos creado el context, asi que por el momento pueden colocar el tipo any.

Pero lo importante ahora es que recibimos la función **setSession**. 

```ts
// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {}
```

Ahora usaremos la función **onAuthStateChanged**, que recibe como primer parámetro el **FirebaseAuth**

```ts
import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth)
}
```

El segundo parámetro es un callback, que retorna el usuario si es que existe su sesión activa, de lo contrario retorna undefined. 

```ts
import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth, user => {

    })
}
```

Evaluamos el usuario:
- Si no existe, usamos el setSession para establecer el status en **no-authenticated** y el id del usuario en null. (**no olviden colocar el return para evitar que se ejecute la siguiente linea**)

- Si existe, usamos el setSession para establecer el status en **authenticated** y el id del usuario.


```ts
import { onAuthStateChanged } from 'firebase/auth'
import { FirebaseAuth } from './config'

// type StateDispatch = React.Dispatch<React.SetStateAction<Pick<AuthStateContext, "status" | "userId">>>
type StateDispatch = any

export const onAuthStateHasChanged = (setSession: StateDispatch) => {
    onAuthStateChanged(FirebaseAuth, user => {

        if (!user) return setSession({ status: 'no-authenticated', userId: null })

        setSession({ status: 'authenticated', userId: user!.uid })
    })
}
```

**Probablemente no entiendas porque mandamos status o userId, bueno eso son los datos que necesitaremos en nuestro estado global, cuando vayamos a crear el contexto de nuestra app.**

### 🔥 4 - Creando la función para cerrar sesión. <a id="17"></a>
Ahora que pasa, gracias a que estamos observando el estado de autenticación del usuario, no podemos cambiar de usuario por un buen rato, ni aunque recargues o cierres el navegador.

Bueno para ello, debemos cerrar sesión, y e muy sencillo:

```ts
import { FirebaseAuth } from './config'

export const logoutFirebase = async () => await FirebaseAuth.signOut()
```

## 🔥 Creando un contexto de nuestra aplicación. <a id="18"></a>
Para crear un contexto primero vamos a crear una carpeta en **src/context** y dentro un archivo nombrado **authContext.tsx**

Dentro vamos a definir nuestra interfaz de las propiedades que vamos a compartir en nuestro contexto.

```ts
export interface AuthStateContext {
    userId: string | null
    status: 'checking' | 'authenticated' | 'no-authenticated'
    handleLoginWithGoogle: () => Promise<void>
    handleLoginWithCredentials: (password: string, email: string) => Promise<void>
    handleRegisterWithCredentials: (password: string, email: string) => Promise<void>
    handleLogOut: () => Promise<void>
}
```

Luego vamos a crear el estado inicial de nuestro contexto.

Por defecto el status estará en **checking** por que al principio no sabemos si esta autenticado o no, lo sabremos una vez que se ejecuten ciertas funciones. Y también el userId sera nulo por defecto hasta comprobar el estado de autenticación del usuario.

```ts
const initialState: Pick<AuthStateContext, 'status' | 'userId'> = {
    status: 'checking',
    userId: null
}
```

Creamos el contexto.
```ts
export const AuthContext = createContext({} as AuthStateContext)
```

Creamos el proveedor que sera un componente funcional que recibirá el children

```tsx
interface IElement { children: JSX.Element | JSX.Element[] }

export const AuthProvider = ({ children }: IElement) => {}
```

Dentro del **AuthProvider** usaremos un estado y lo inicializamos con el objeto que establecimos con anterioridad.
```tsx

export const AuthProvider = ({ children }: IElement) => {

    const [session, setSession] = useState(initialState)
}
```

Ahora vamos a usar la función que creamos antes para observar el estado de autenticación del usuario.
Lo haremos en un efecto que solo se debe ejecutar la primera vez que inicie la aplicación. Y el callback que le mandaremos sera el **setSession**.

```tsx
const [session, setSession] = useState(initialState)

useEffect(() => {
    onAuthStateHasChanged(setSession)
}, [])
```

Luego haremos la función que ejecuta el cierre de sesión. Llamamos a la función logoutFirebase y establecemos la session con el userId en nulo y el status en **no-authenticated**

```ts
import { logoutFirebase } from '../firebase/providers'

const handleLogOut = async () => {
    logoutFirebase()
    setSession({ userId: null, status: 'no-authenticated' })
}
```

Después, haremos una función que reutilizaremos en otras funciones, ya que queremos evitar repetir tanto código.

Esta función recibe el userId que puede ser un string o undefined, evaluamos si el userId es un string:
- Si el userId es un string, significa que el usuario esta autenticado y establecemos la sesión con el userId y el status en **authenticated**. (**no olviden colocar el return para evitar que se ejecute la siguiente linea**)

- Si el userId es undefined, llamamos a la función **handleLogOut**, ya que el usuario no tiene una autenticación valida, y necesitamos cerrar todas las sesiones.

```ts
const validateAuth = (userId: string | undefined) => {
    if (userId) return setSession({ userId, status: 'authenticated' })
    handleLogOut()
}
```

Otra función que también vamos a reutilizar es la siguiente, es para establecer el status en **checking** mientras se realizar alguna validación.

```ts
const checking = () => setSession(prev => ({ ...prev, status: 'checking' }))
```

**Las siguientes funciones, asi como esta serán parecidas, primero hacer el checking, luego ejecutar el función especifico para esta tarea el cual nos retorna el userId o undefined y llamar el validateAuth mandando lo que retorna dicha función**

1 - Función para iniciar sesión con Google.

```ts
import { singInWithGoogle } from '../firebase/providers'

const handleLoginWithGoogle = async () => {
    checking()
    const userId = await singInWithGoogle()
    validateAuth(userId)
}
```
2 - Función para iniciar sesión con credenciales.

```ts
const handleLoginWithCredentials = async (password: string, email: string) => {
    checking()
    const userId = await loginWithCredentials({ email, password })
    validateAuth(userId)
}
```

3 - Función para crear cuenta con credenciales.

```ts
const handleRegisterWithCredentials = async (password: string, email: string) => {
    checking()
    const userId = await signInWithCredentials({ email, password })
    validateAuth(userId)
}
```

Finalmente, como es un componente funcional, debemos regresar un componente de la siguiente manera, colocando el children.

```tsx
return (
    <AuthContext.Provider>
        {children}
    </AuthContext.Provider>
)
```

La etiqueta AuthContext.Provider recibe un **value**, que son las siguientes propiedades

```tsx
return (
    <AuthContext.Provider  
        value={{
            ...session,
            handleLoginWithGoogle,
            handleLoginWithCredentials,
            handleRegisterWithCredentials,
            handleLogOut
        }}
    >
        {children}
    </AuthContext.Provider>
)
```

Y terminamos con nuestro contexto. Se vería asi 👀

```tsx
import { createContext, useEffect, useState } from 'react'
import { loginWithCredentials, logoutFirebase, onAuthStateHasChanged, signInWithCredentials, singInWithGoogle } from '../firebase/providers'

export interface AuthStateContext {
    userId: string | null
    status: 'checking' | 'authenticated' | 'no-authenticated'
    handleLoginWithGoogle: () => Promise<void>
    handleLoginWithCredentials: (password: string, email: string) => Promise<void>
    handleRegisterWithCredentials: (password: string, email: string) => Promise<void>
    handleLogOut: () => Promise<void>
}

const initialState: Pick<AuthStateContext, 'status' | 'userId'> = {
    userId: null,
    status: 'checking'
}

export const AuthContext = createContext({} as AuthStateContext)

interface IElement { children: JSX.Element | JSX.Element[] }

export const AuthProvider = ({ children }: IElement) => {

    const [session, setSession] = useState(initialState)

    useEffect(() => {
        onAuthStateHasChanged(setSession)
    }, [])

    const handleLogOut = async () => {
        logoutFirebase()
        setSession({ userId: null, status: 'no-authenticated' })
    }

    const validateAuth = (userId: string | undefined) => {
        if (userId) return setSession({ userId, status: 'authenticated' })
        handleLogOut()
    }

    const checking = () => setSession(prev => ({ ...prev, status: 'checking' }))

    const handleLoginWithGoogle = async () => {
        checking()
        const userId = await singInWithGoogle()
        validateAuth(userId)
    }

    const handleLoginWithCredentials = async (password: string, email: string) => {
        checking()
        const userId = await loginWithCredentials({ email, password })
        validateAuth(userId)
    }

    const handleRegisterWithCredentials = async (password: string, email: string) => {
        checking()
        const userId = await signInWithCredentials({ email, password })
        validateAuth(userId)
    }

    return (
        <AuthContext.Provider
            value={{
                ...session,
                handleLoginWithGoogle,
                handleLoginWithCredentials,
                handleRegisterWithCredentials,
                handleLogOut
            }}
        >
            {children}
        </AuthContext.Provider>
    )
}
```

Ahora necesitamos envolver nuestra app con el proveedor. Para ello vamos al punto mas alto de nuestra app que es el archivo `src/main.tsx` y agregamos el AuthProvider

```tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { AuthProvider } from './context/authContext'
import './index.css'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>
)
```

## 🔥 Usando nuestro contexto. <a id="19"></a>
Ahora nos vamos a el archivo `src/components/Register.tsx` y usamos nuestro contexto de la siguiente manera:
Importamos el hook **useContext** y le mandamos el **AuthContext** y obtenemos la función **handleRegisterWithCredentials**

Dicha función la ejecutamos dentro de handleSubmit y le mandamos el email y password.

```ts
import { useContext } from 'react';
import { AuthContext } from "../context/authContext";

export const Register = () => {

    const { handleRegisterWithCredentials } = useContext(AuthContext)

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: 'test@test2.com',
            pass: '123456'
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        handleRegisterWithCredentials(pass, email)
    }

// ...
```

Lo mismo hacemos en `src/components/Login.tsx`.

```tsx
import { useContext } from 'react';
import { AuthContext } from '../context/authContext';

export const Login = () => {

    const { handleLoginWithCredentials } = useContext(AuthContext)

    const { handleChange, pass, email } = useForm({
        initialState: {
            email: 'test@test1.com',
            pass: '123456'
        }
    })

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        handleLoginWithCredentials(pass, email)
    }

// ...
```

Pero también necesitamos la función **handleLoginWithGoogle** 

```ts
const { handleLoginWithGoogle, handleLoginWithCredentials } = useContext(AuthContext)
```

Dicha función se va a ejecutar el atributo onClick de la etiqueta button de Google.

```tsx
<button type="button" onClick={handleLoginWithGoogle}> Google </button>
```

Finalmente en nuestro archivo `src/App.tsx`

Vamos a usar el contexto extrayendo el status y el userID.
Evaluamos el status y si es checking, mostramos un loading.

```tsx
  const { status, userId } = useContext(AuthContext)

  if (status === 'checking') return <p className="loading"><span>Checking credentials, wait a moment...</span></p>
```

![loading](https://res.cloudinary.com/dnxchppfm/image/upload/v1663804708/firebase-auth-react/03-app_gr54fw.png)

Ahora al final del archivo crearemos dos componentes.

El primero es el HomePage (para simular que es un pagina diferente).

Este componente solo sera visible cuando el usuario este autenticado.

Y se mostrara el userID y un botón que ejecuta el cerrar session.
```tsx
export const HomePage = () => {
  const { userId, handleLogOut } = useContext(AuthContext)

  return (
    <section>
      <h5>Your ID is: <span>{userId}</span></h5>
      <button className="btn-logout" onClick={handleLogOut}>Log out</button>
    </section>
  )
}
```
![authenticated](https://res.cloudinary.com/dnxchppfm/image/upload/v1663804710/firebase-auth-react/04-app_yrhqyy.png)

El segundo componente es el AuthPage, (simula otra pagina diferente).

Este componente solo sera visible cuando el usuario NO este autenticado.

Solo se muestran los componentes Login y Register que teníamos en nuestro componente App.

```tsx
export const AuthPage = () => {
  return (
    <section>
      <Login />
      <Register />
    </section>
  )
}
```

Ahora en el componente App, vamos hacer una validación. Donde si el status, es authenticated y existe el userId, mostramos el HomePage, de lo contrario que muestre el AuthPage

```tsx

return (
    <main>
        <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
        {
            (status === 'authenticated' && userId)
                ? <HomePage />
                : <AuthPage />
        }
    </main>
)
```

El archivo App.tsx quedaría de la siguiente manera 👀:

```tsx
import { useContext } from "react"
import { Login, Register } from "./components"
import { AuthContext } from './context/authContext';

const App = () => {

  const { status, userId } = useContext(AuthContext)

  if (status === 'checking') return <p className="loading"><span>Checking credentials, wait a moment...</span></p>

  return (
    <main>
      <h1><b>Auth with</b> <span>Firebase</span> <b>and</b> <span>React</span></h1>
      {
        (status === 'authenticated' && userId)
          ? <HomePage />
          : <AuthPage />
      }
    </main>
  )
}
export default App

export const HomePage = () => {
  const { userId, handleLogOut } = useContext(AuthContext)

  return (
    <section>
      <h5>Your ID is: <span>{userId}</span></h5>
      <button className="btn-logout" onClick={handleLogOut}>Log out</button>
    </section>
  )
}

export const AuthPage = () => {
  return (
    <section>
      <Login />
      <Register />
    </section>
  )
}
```


## 🔥 Conclusión. <a id="20"></a>

Sin duda usar un servio como Firebase que nos ayuda mucho en ahorrar tiempo a comparación de construir nuestros propios servicios como la autenticación. 

Espero que te haya gustado esta publicación y que te haya ayudada a entender más sobre como realizar la autenticación de usuarios en tus aplicación con React. 🤗

Si es que conoces alguna otra forma distinta o mejor de realizar esta funcionalidad con gusto puedes comentarla 🙌.

> **Te invito a que revises mi portafolio en caso de que estés interesado en contactarme para algún proyecto! [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 No olvides seguirme también en twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

{%embed https://twitter.com/Frankomtz361/status/1575329958674796547?s=20&t=AVAVQ1am_AkFb2TyMo9S-A %}

### 🔥 Código fuente. <a id="21"></a>

{%embed https://github.com/Franklin361/auth-firebase-react %}