Protección de rutas con React Router Dom. 💪
Tags: react, spanish, javascript, programming
Published: 2022-10-14 13:32:58

En esta ocasión vamos a usar la librearía de react router dom para poder crear rutas protegidas en nuestra aplicación de React JS.

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

>**⚠️ Nota**: Es necesario que cuentes con nociones de React Router Dom.

&nbsp; 

## Tabla de contenido
> 📌 [Tecnologías a utilizar.](#1)  
> 📌 [Creando el proyecto.](#2)  
> 📌 [Primeros pasos.](#3)  
> 📌 [Creando las paginas.](#4)  
> 📌 [Integrando React Router.](#5)  
> 📌 [Agregando protección de rutas.](#6)  
> 📌 [Conclusión.](#7)  
>> 📌 [Código fuente.](#8)  

&nbsp; 


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

- ▶️ React JS (version 18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ React Router Dom (version 6)
- ▶️ 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: **`protected-routes`** (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 protected-routes
```
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>
Vamos al archivo `src/App.tsx` y borramos todo el contenido para crear un nuevo componente. Que por el momento solo renderizar un "hola mundo"

```tsx
const App = () => {
  return (
    <>
        <div>Hello world</div>
    </>
  )
}
export default App
```

Lo que sigue es crear un Layout, esto solo es con propósito de estética para la app, no es obligatorio.

> 🚨 Nota: Cada vez que creamos una nueva carpeta, también crearemos un archivo **index.ts** para agrupar y exportar todas las funciones y componentes de otros archivos que están dentro de la misma carpeta, y que dichas funciones puedan ser importadas a traves de una sola referencia a esto se le conoce como **archivo barril**. 

Creamos la carpeta `src/components` y dentro creamos el archivo **Layout.tsx** para agregar:

```tsx
interface Props { children: JSX.Element | JSX.Element[] }
export const Layout = ({ children }: Props) => {
    return (
        <div>
            <h1>Protected Routes with <br /> <span>React Router</span></h1>
            {children}
            <div className="logo">
                <img src="https://cdn.svgporn.com/logos/react-router.svg" alt="react-router" />
            </div>
        </div>
    )
}
```
Luego lo importamos en el archivo `src/App.tsx`

```tsx
import { Layout } from "./components"
import { AppRouter } from "./routes"

const App = () => {
  return (
    <Layout>
      
    </Layout>
  )
}
export default App
```
Debería lucir así 👀

![first](https://res.cloudinary.com/dnxchppfm/image/upload/v1664292080/protected-routes/00_aearmj.png)

## 🔒 Creando las paginas. <a id="4"></a>

Creamos la carpeta `src/pages` y dentro creamos dos archivos:

- **HomePage.tsx**, que actuara como la pagina que queremos que sea privada, o sea que solo usuarios con alguna autenticación puedan visualizar dicha pagina, incluso si el usuario sabe la  URL de la pagina, no debe de poder verla.

```tsx
export const HomePage = () => {
    return (
        <div className="page">HomePage</div>
    )
}
```

- **LoginPage.txs**, que actuara como la pagina publica que cualquiera que no tenga autenticación pueda ver, pero si el usuario ya tiene una autenticación entonces esta pagina no debe ser visible ni conociendo la URL.

```tsx
export const LoginPage = () => {
    return (
        <div className="page">LoginPage</div>
    )
}
```

**Aun no vamos a importar en ningún lado estas dos paginas.**

> Cabe mencionar que no se realizara ningún método de autenticación o algo parecido.

## 🔒 Agregando React Router. <a id="5"></a>

Primero tenemos que instalar [react router dom](https://reactrouter.com/en/main):

```bash
npm install react-router-dom
```

Después creamos una carpeta `src/routes` y creamos el archivo **AppRouter.tsx**.

Este archivo sera el root de nuestro router, de aquí se desencadenaran las demás rutas.

Creamos un componente funcional.

```tsx
export const AppRouter = () => {
    return ()
}
```

Tenemos que importar algunos componentes que nos ofrece react-router-dom.

- **BrowserRouter**, este componente tiene que envolver toda nuestra aplicación ya que es un proveedor. Una opción también es colocarlo en el archivo `src/main.tsx`, pero en mi caso me gustaría ponerlo en este archivo AppRouter.tsx y como este es el root de las rutas entonces esto es lo que vamos a importar en `src/App.tsx`

- **Routes**, este componente debe estar dentro del **BrowserRouter**, y es el contenedor de las rutas que maneja nuestra aplicación, y cada vez que la ubicación cambia, este componente busca entre todas sus rutas hijas, para encontrar una ruta que coincida con la ubicación y poder renderizar el elemento que expone esa ruta hija.

- **Route**, este componente es donde se definirá la ruta (mediante la prop **path**) y el elemento a renderizar (mediante la prop **element** pasando un elemento JSX). 
    - Cuando colocamos un **'*'** en la prop path, significa que en caso de que ninguna ruta anterior conicidad con la ubicación, entonces podemos renderizar una pagina 404, pero en este caso, se manda al usuario a otra ubicación.

- **Navigate**, este componente sirve para cambiar la ubicación actual cuando se renderiza.
    - **to**, es la prop para colocar el path hacia donde quieres ir en tu aplicación.
    - **replace** es la prop para quitar del historial de navegación la actual ruta, y colocar la nueva ruta a la que te diriges. O sea que cuando quieras volver atrás, esa ya no estará la anterior pagina.

```tsx
  import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"

export const AppRouter = () => {
    return (
        <BrowserRouter>
            <Routes>
                <Route path='/' element={<HomePage />} />
                <Route path='login' element={<LoginPage />} />

                <Route path='*' element={<Navigate to='/login' replace />} />
            </Routes>
        </BrowserRouter>
    )
}
```

Listo, aquí tenemos 
Y ahora necesitamos hacer cambios en el **AppRouter.tsx**or el momento nuestro root router.



Ahora vamos a colocarlo en `src/App.tsx`

```tsx
import { Layout } from "./components"
import { AppRouter } from "./routes"

const App = () => {
  return (
    <Layout>
      <AppRouter />
    </Layout>
  )
}
export default App
```


Notaras que va a renderizar el **HomePage.tsx**, esto es porque cuando entramos a nuestra app, por defecto el path **/** y coincide con la ruta que renderiza el **HomePage.tsx**

![home](https://res.cloudinary.com/dnxchppfm/image/upload/v1664292080/protected-routes/02_ruzzbi.png)

Y si en la barra de dirección colocas **/login**, entonces te muestra el **LoginPage.tsx**.

Pero si colocas otra cosa que no sea un simple **/** o **/login**, entonces por defecto seria * y renderiza el componente Navigate que te manda al **/login**.

## 🔒 Agregando protección de rutas. <a id="6"></a>

Por defecto, puedes visualizar el HomePage.tsx pero no debería ser posible, ya que no tienes una "autenticación". Asi que vamos a convertir esta ruta en una ruta privada. 

Vamos a crear un nuevo archivo en `src/routes` nombrado **PrivateRoutes.tsx**

Y dentro vamos a crear una nuevo conjunto de rutas, que serán nuestras rutas privadas.

Para anidar rutas necesitamos tanto el contenedor de rutas que es el componente **Routes** y las rutas como tal que son el componente **Route**

Aquí irán todas las rutas que queremos que sean privadas. Solamente movemos la ruta que renderiza el HomePage a este componente.

Y ademas agregaremos una nueva ruta para mandar al usuario en caso de que el usuario ingrese una ruta que no exista en .nuestra app.

Con el comodín **'*'** en la prop  

```tsx
import { Navigate, Route, Routes } from 'react-router-dom';
import { HomePage } from '../pages';

export const PrivateRoutes = () => {
    return (
        <Routes>
            <Route path='/' element={<HomePage />} />
            <Route path='*' element={<Navigate to='/' replace />} />
        </Routes>
    );
};
```

Y ahora necesitamos hacer cambios en el **AppRouter.tsx**

Vamos a simular la autenticación con una simple variable (aunque en una app real lo mas probable es que venga de un API), lo importante es que tengas acceso a algún valor que te indique si el usuario esta autenticado o no y puedas usar dicho valor en el **AppRouter.tsx**:

```tsx
type Status = 'checking' | 'authenticated' | 'no-authenticated'

let status: Status = 'no-authenticated'
```

En el caso de la opción **checking** es cuando inicia la app y no sabes si el usuario esta autenticado o no. En ese caso puedes renderizar un loading.
```tsx
if (status === 'checking') return <div className="loading">Checking credentials...</div>
```
![loading](https://res.cloudinary.com/dnxchppfm/image/upload/v1664292080/protected-routes/01_ku1qsa.png)


Y ahora haremos una condición dentro del contenedor de Rutas, donde si el usuario esta autenticado entonces déjalo ver las rutas privadas y no muestres el login que en este punto sera innecesario ya que el usuario ya esta autenticado. 

De lo contrario, no renderize el componente de rutas privadas y muestra el LoginPage

Y al final, después de la condición, seguimos manteniendo la ruta con el comodín [*] ya que si el usuario ingresa una ruta que no exista entonces, hay que mandarlo a alguna parte que si existe de nuestra app.

> Nota que en la ruta donde usamos [*] el path de la prop **to** es **/login** y no es simplemente **login**. Ya que si colocamos solo la palabra **login** entonces mantendrá la URL como esta y solo le adicionara /login, lo cual no queremos.

> Por ejemplo, si estamos en **http://localhost:5173/about** y queremos mandar al usuario a login, y en el componente Navigate en la prop **to** solo colocamos **login** sin el **/** al inicio entonces la URL resultante seria **http://localhost:5173/about/login** pero nosotros queremos apuntar al root de nuestras rutas y es por eso que debemos colocar el símbolo **/** al inicio, para que te envié a la URL **http://localhost:5173/login**

```tsx
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"
import { PrivateRoutes } from "./"
import { LoginPage } from '../pages';

type Status = 'checking' | 'authenticated' | 'no-authenticated'

let status: Status = 'no-authenticated'

export const AppRouter = () => {

    if (status === 'checking') return <div className="loading">Checking credentials...</div>

    return (
        <BrowserRouter>
            <Routes>
                {
                    status === 'authenticated'
                        ? <Route path="/*" element={<PrivateRoutes />} />
                        : <Route path="login" element={<LoginPage />} />
                }

                <Route path='*' element={<Navigate to='/login' replace />} />
            </Routes>
        </BrowserRouter>
    )
}
```

> También nota que el path de las rutas privadas es **'/*'** significa que cualquier ruta que coincida con el / entonces entra al componente **PrivateRoutes**.

> Un ejemplo seria: tienes la ruta about/* y alguien coloca el en buscador about/me entonces eso seria permitido y accedería al elemento que renderiza esa ruta. Si quitaras el comodín [*] entonces la ruta about/me ya NO sera permitido.

Hasta aquí ya tendríamos nuestras rutas privadas, y una sola ruta "publica" pero que tal si tenemos mas rutas publicas como la de crear usuario, entonces debemos crear un nuevo componente llamado **PublicRoutes.tsx**

Y agregar las rutas que queremos que sean "publicas" o sea disponibles cuando el usuario no este autenticado.

```tsx
import { Navigate, Route, Routes } from 'react-router-dom';
import { LoginPage } from '../pages';

export const PublicRoutes = () => {
    return (
        <Routes>
            <Route path='login' element={<LoginPage />} />
            <Route path='*' element={<Navigate to='/login' replace />} />
        </Routes>
    );
};
```

Y asi quedaría nuestra **AppRouter.tsx**.

```tsx
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"
import { PrivateRoutes, PublicRoutes } from "./"

type Status = 'checking' | 'authenticated' | 'no-authenticated'

let status: Status = 'authenticated'

export const AppRouter = () => {

    if (status === 'checking') return <div className="loading">Checking credentials...</div>

    return (
        <BrowserRouter>
            <Routes>
                {
                    status === 'authenticated'
                        ? <Route path="/*" element={<PrivateRoutes />} />
                        : <Route path="/*" element={<PublicRoutes />} />
                }

                <Route path='*' element={<Navigate to='/login' replace />} />

            </Routes>
        </BrowserRouter>
    )
}
```

Notaras que ambas rutas tienen el mismo path, pero no sera un problema ya que su existencia es condicional, o solo existirá una ruta siempre.

Por cierto, si necesitas que alguna ruta sea accedida con o sin autenticación entonces colócala en el AppRouter.tsx después de la condición.

Y asi tendríamos rutas tanto publicas y privadas. 🥳

## 🔒 Conclusión. <a id="7"></a>

Las rutas privadas son de mucha relevancia hoy en dia en las aplicaciones. Y con ayuda de React Router Dom nos hace mucho mas fácil la implementación de dicho proceso.

Otra idea que te puedo dar es que en las rutas privadas puedes anidar otras rutas para dividirlos por rol por ejemplo, si un usuario tiene el rol de 'user' no podrá ver la pagina de Admin, pero si tiene el rol de 'admin' entonces si podrá ver dicha pagina. 

Espero que te haya gustado esta publicación y que te haya ayudada a entender más sobre como usar React Router Dom para implementar la protección de rutas. 🤗

Esta forma de hacer rutas privadas, no es la única, 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="8"></a>

{%embed https://github.com/Franklin361/protected-routes %}