Autenticación simple con React y Redux Toolkit

Hoy quiero compartir contigo, la manera de usar Redux Toolkit para un inicio de sesión en React.

Bryan Aguilar
9 min readMay 23, 2022

En este artículo te voy a enseñar a utilizar Redux Toolkit en un proyecto React JS con Typescript y creado con Vite JS.

¿Por qué utilizar Vite JS?

Vite JS es una herramienta del frontend desarrollada por los creadores de Vue JS que permite crear aplicaciones de Javascript con algún framework o libreria como Vanila, ReactJs, Vuejs, Svelte(tanto en versión Javascript, como Typescript). Gracias a su característica de Instant Server Start (Inicio Instantáneo del servidor), a diferencia de create react app, Vite ejecuta la aplicación mucho más rápido y los cambios reflejados de manera instantánea.

Creación del proyecto

En mi caso usaré el gestor de paquetes, yarn, pero si deseas lo puedes hacer con npm, puedes observar una guía detallada en la documentación oficial.

Creamos nuestra aplicación

yarn create vite

Este comando solicitará el nombre de tu proyecto, el framework con en que vas a trabajar (en nuestro caso React), y finalmente te preguntará si deseas usar Typescript.

Una vez que el proyecto se haya creado, accedemos al directorio de la aplicación y ejecutamos el comando yarn o npm i para obtener todas las dependencias. En mi caso:

cd simple-auth/
yarn

En este punto puede probar la correcta ejecución de tu aplicación, si estas usando yarn, con el siguiente comando:

yarn run dev

Nota la rapidez con la que se ejecutó tu aplicación!! 😎

Dependencias necesarias

  • axios: Librería utilizada para hacer solicitudes HTTP
  • eslint-plugin-react-hooks: Nos ayuda a controlar errores y nos brinda ayuda cuando tengamos algún error de codificación de nuestros hooks.
  • redux: Gestor de estados de la aplicación.
  • sass: Permite usar SASS en lugar de simple CSS (si instalas esta extensión asegúrate de cambiar las extensiones de tus ficheros css a scss, además reemplaza la extensión importaciones en los ficheros App.tsx y main.tsx)
  • bootstrap: Framework de CSS
  • react-hook-form: Librería para el manejo y validación de formularios
  • react-router-dom: Gestor de rutas para React

Instalación de dependencias

yarn add axios @reduxjs/toolkit sass bootstrap react-hook-form react-router-dom @types/node react-redux

Instalación de dependencias de desarrollo

yarn add eslint-plugin-react-hooks --dev

Configuraciones generales

Como hemos instalado Boostrap 5 en nuestro proyecto es necesario hacer las importaciones necesarias:

  • En el archivo App.scss importamos los stilos de Boostrap
@import "node_modules/bootstrap/scss/bootstrap";
  • En el archivo main.tsx importamos el Javascript de Bootstrap (ya que estamos usando Boostrap 5, no es necesario Jquery)
  • En el mismo archivo también configuramos el Router de nuestra aplicación, esto nos permitirá manejar rutas.
import React from "react";
import ReactDOM from "react-dom";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import { HashRouter } from "react-router-dom";
import "./index.scss";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>,
document.getElementById("root")
);

Estructura de directorios

En mis proyectos suelo utilizar la siguiente estructura de directorios:

├── src/
├── assets/
│ ├── images/
│ ├── icons/
│ ├── fonts/
│ ├── ...
├── components/
│ └── Component1.component.tsx
├── data/
├── hooks/
├── layout/
├── models/
├── pages/
│ └── page1/
│ ├── components/
│ └── Page1.page.tsx
├── redux/
│ ├── ...
│ ├── store.tsx
├── routes/
│ ├── routes.tsx
├── services/
├── utilities/
...
├── App.scss
├── App.tsx
├── favicon.ico
├── index.scss
└── main.tsx
...
  1. El directorio assets contiene todos los recursos multimedia como imágenes, fuentes, íconos, incluso aquí se pueden agregar archivos para la internacionalización de la aplicación en un directorio i18n.
  2. El directorio components contiene todos los componentes globales de tu aplicación, por ejemplo: alertas personalizadas, laoders o componentes de paginación.
  3. El directorio data contiene constantes que puedes usar en tu aplicación, aquí puedes incluir rutas, o enumeraciones.
  4. El directorio hooks contiene hooks personalizados que ayudarán a abstraer lógica de tu aplicación.
  5. El directorio layout contiene los elementos principales por los que se conforma tu aplicación, por lo general suelen ser cuatro: Header, Sidebar, Footer y Skeleton.
  6. El directorio models contiene los modelos que utilizarás en tu aplicación, ya que estamos usando Typescript, es importante mantener un correcto tipado en toda la aplicación.
  7. El directorio pages contiene las páginas por las que está conformada tu aplicación, yo acostumbro a crear un directorio por cada conjunto de páginas (por ejemplo el directorio users contendrá las páginas: UsersList.page.tsx, UsersCreate.page.tsx y UserDetails.page.tsx) además puedes crear un subdirectorio componente en caso de que todas estas páginas compartan componentes.
  8. El directorio redux contiene la configuración del estado global de nuestra aplicación, esto lo veremos más adelante.
  9. El directorio routes contiene un archivo con la definición de las rutas de tu aplicación.
  10. El directorio services contiene la definición de axios para las peticiones HTTP, aquí puedes crear interceptores que te ayuden a agregar cabeceras o manejar los errores de mejor manera.
  11. Finalmente, el directorio utilities lo puedes usar para almacenar funciones globales en tu aplicación, por ejemplo: una función de conversión de divisas, un formateador de fechas, etc.

Autenticación simple usando Redux Toolkit

Para nuestro ejemplo vamos a crear dos páginas sencillas:

  • src/pages/home/Home.page.tsx: Esta será la página principal de la aplicación que estará protegida por un inicio de sesión previo.
  • src/pages/auth/Login.page.tsx: Esta será la página de inicio de sesión para nuestra aplicación.

Además, creamos nuestros componentes del Layout, vamos a crear los siguientes componentes:

  • src/components/header.layout.tsx
  • src/components/footer.layout.tsx
  • src/components/skeleton.layout.tsx
import { ReactChild, ReactChildren } from "react";
import Header from "./header.layout";
import Footer from "./footer.layout";
interface Props {
children: ReactChild | ReactChild[] | ReactChildren | ReactChildren[];
}
const Skeleton = ({ children }: Props): JSX.Element => {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
};
export default Skeleton;

Tenemos que definir las rutas de nuestra aplicación, en nuestro caso solo tenemos la ruta / que conduce a la página principal, pero tu debes agregar todas las rutas de tu aplicación.

src/routes/app.routes.tsx

import { Navigate, Route, Routes } from "react-router-dom";
import Skeleton from "../layout/skeleton.layout";
import HomePage from "../pages/home/Home.page";
const AppRoutes = (): JSX.Element => {
return (
<Skeleton>
<Routes>
<Route path="/" element={<HomePage />} />
{/* Redirect to route */}
<Route path="*" element={<Navigate replace to="/" />}/>
</Routes>
</Skeleton>
);
};
export default AppRoutes;

Ahora, debemos definir las rutas principales en el fichero src/App.tsx, indicamos que si la ruta es cualquier otra que no sea login redireccionaremos al usuario a las rutas protegidas con un inicio de sesión. En este punto puedes agregar todas las rutas públicas de tu aplicación.

import { Route, Routes } from "react-router-dom";
import "./App.scss";
// pages
import LoginPage from "./pages/auth/Login.page";
import AppRoutes from "./routes/app.routes";
function App(): JSX.Element {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="*" element={<AppRoutes />} />
</Routes>
);
}
export default App;

Ya que vamos a usar redux en nuestra aplicación es importante definir el Provider con el estado global de la aplicaición, esto lo realizamos en el fichero src/main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import "bootstrap/dist/js/bootstrap.bundle.min.js";
import { HashRouter } from "react-router-dom";
import App from "./App";
import "./index.scss";
import { Provider } from "react-redux";
import store from "./redux/store";
ReactDOM.createRoot(document.getElementById("root")!).render(
<Provider store={store}>
<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
</Provider>
);

Empezaremos configurando redux para nuestra aplicación. Toda la configuración que veràs a continuación se basa en la documentación oficial de redx toolkit, piedes verla aquí.

Definimos del archivo src/redux/store.tsx, este archivo contiene la definición del estado de nuestra aplicación. Inicialmente contendrá:

import { configureStore } from "@reduxjs/toolkit";const store = configureStore({
reducer: {

},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export default store;// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Aquí definimos los reducers que forman parte del estado global de la aplicación.

Para el manejo de la autenticación, suelo crear un directorio src/redux/auth. Además, definimos una interfaz para el manejo del estado, por lo regular suelo agregar una variable booleana que indicará si la petición HTTP está siendo realizada o si ya finalizó, y una variable de error en el caso de que exista un error durante la petición.

export interface AuthState {
isLoading: boolean;
error?: any;
userInfo: Auth | undefined;
}

A partir de esta interfaz defino el estado inicial para la autenticación.

const initialState: AuthState = {
isLoading: false,
userInfo: getUserFromLocalStorage(),
};

Para la autenticación estoy usando el LocalStorage (puedes revisar la función getUserFromLocalStorage() en el repositorio de Github). Posteriormente definimos el authenticationSlice donde se definen los diferentes estados que puede tener la aplicación, además se indica el estado inicial.

Recuerda que todo esto se basa en la documentación oficial de Redux Tookit.

export const authenticationSlice = createSlice({
name: "authentication",
initialState,
reducers: {
request: (state): AuthState => {
return {
...state,
isLoading: true,
};
},
success: (state, action: PayloadAction<any>): AuthState => {
return {
...state,
isLoading: false,
userInfo: action.payload,
};
},
fail: (state, action: PayloadAction<any>): AuthState => {
return {
...state,
isLoading: false,
error: action.payload,
};
},
logout: (state): AuthState => {
return {
...state,
isLoading: false,
userInfo: undefined,
};
},
},
});

Posteriormente definimos las funciones de authLogin y authLogout que harán uso de los diferentes estados que hemos definido anteriormente.

Este es el resultado final del archivo src/redux/auth/auth.slice.tsx

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Auth } from "../../models/auth.model";
import { getUserFromLocalStorage, removeUserFromLocalStorage, setUserLocalStorage } from "../../services/persistUser.service";
import { RootState } from "../store";
import { AppThunk, AppThunkDispatch } from "../utils/types";
export interface AuthState {
isLoading: boolean;
error?: any;
userInfo: Auth | undefined;
}
const initialState: AuthState = {
isLoading: false,
userInfo: getUserFromLocalStorage(),
};
export const authenticationSlice = createSlice({
name: "authentication",
initialState,
reducers: {
request: (state): AuthState => {
return {
...state,
isLoading: true,
};
},
success: (state, action: PayloadAction<any>): AuthState => {
return {
...state,
isLoading: false,
userInfo: action.payload,
};
},
fail: (state, action: PayloadAction<any>): AuthState => {
return {
...state,
isLoading: false,
error: action.payload,
};
},
logout: (state): AuthState => {
return {
...state,
isLoading: false,
userInfo: undefined,
};
},
},
});
export const authLogin = (email: string, password: string): AppThunk =>
async (dispatch: AppThunkDispatch): Promise<void> => {
try {
dispatch(request());
const data: Auth = {
accessToken: 'token_simple_auth_app',
user: {
id: '123456789',
name: 'Bryan',
lastname: 'Aguilar'
}
}
dispatch(success(data));
setUserLocalStorage(data);
} catch (error: any) {
dispatch(fail(error));
}
};
export const authLogout =
(): AppThunk =>
async (dispatch: AppThunkDispatch): Promise<void> => {
dispatch(logout());
removeUserFromLocalStorage();
};
export const { request, success, fail, logout } = authenticationSlice.actions;
export const selectAuth = (state: RootState): AuthState => state.authentication;
export const authenticationReducer = authenticationSlice.reducer;

Una vez definido el estado de la autenticación en el archivo auth.slice.tsx inyectamos su reducer en el store global de la aplicación (src/redux/store.tsx), el resultado final es el siguiente:

import { configureStore } from "@reduxjs/toolkit";
import { authenticationReducer } from "./auth/auth.slice";
const store = configureStore({
reducer: {
authentication: authenticationReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export default store;export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Una vez configurado el store de la aplicaicón podemos pasar al diseño y funcionalidad de nuestras páginas.

Para el inicio de sesión he creado un simple formulario usando componentes de Boostrap y react-hook-form. Además he hecho uso de las funciones de inicio de sesión definidas en src/redux/auth/auth.slice.tsx .

El uso de hooks como el useEffect es importante, si aún desconoces el funcionamiento de este hook te recomiendo leer sobre este Hook aquí.

Importante: Como se trata una aplicación sencilla de demostraciòn podrás usar cualquier email o contraseña cuando pruebes la aplicación.

Si tienes alguna duda en cuanto al menejo de las rutas, pudes revisar los archivos src/App.tsx y src/routes/app.routes.tsx. Para resumirlo, las rutas públicas como el login se definen en src/App.tsx mientras que las rutas privadas o que necesitas una autenticación previa se definene en src/routes/app.routes.tsx.

Recuerda que para esta demo app estamos usando el localstorage para mantener la sesión del usuario, las funciones encargadas del manejo del localstorage se encuentran en src/services/persistUser.service.tsx.

Comprobación

En este punto podemos verificar que la aplicación funcione correctamente.

Ejecución de la aplicación

Puedes encontrar el código completo de esta aplicación en mi repositorio de github recuerda que antes debes tener todo configurado (yarn y node instalados) para que funcione correctamente.

Código fuente de este proyecto

https://github.com/baguilar6174/react-simple-auth-app

Puedes encontrar este y otros en mi repositorio de Github . No olvides visitar mi página web.

¡Gracias por leer este artículo!

Si deseas hacerme alguna pregunta, no lo dudes!. Mi bandeja de entrada siempre estará abierta. Ya sea que tengas una pregunta o simplemente me quieras saludar, ¡haré todo lo posible para responderle!

--

--

Bryan Aguilar

Senior Software Engineer · Systems Engineer · Full Stack Developer · Enthusiastic Data Analyst & Data Scientist | https://www.bryan-aguilar.com