Crear un hook personalizado en React o React Native

Tiempo de lectura: 3 minutos

Un hook en React es una función especial que te permite «enganchar» características de React (como el estado y el ciclo de vida) en componentes funcionales. Los dos hooks más comunes son useState (para gestionar el estado) y useEffect (para manejar efectos secundarios), aunque puedes crear tus propios hooks personalizados para encapsular lógica reutilizable.

Bonito rio con puente - Pexels

Aquí tienes una guía paso a paso para crear un hook personalizado en React:

1. Estructura Básica de un Hook Personalizado

Un hook personalizado es una función de JavaScript que sigue el formato useNombreDelHook. Para que React lo reconozca como un hook, el nombre de la función debe comenzar con use.

// Ejemplo de un hook personalizado
export const useUtils = () => {
    const context = useContext(UtilsContext);
    if (!context) {
        throw new Error('useUtils must be used within a UtilsProvider');
    }
    return context;
};

2. Ejemplo Práctico: Hook que contiene un modal

Aquí crearemos un hook personalizado llamado useUtils que contendrá un modal. Además nos servirá para añadir el resto de elementos utils que podamos necesitar.

Paso 1: Crear el Hook

Crea un archivo llamado utilsContext.tsx (estoy usando Typescript):

import AlertDialog, { mostrarAlerta as mostrarAlertaFunc, AlertParams } from '@/components/elements/Dialog';

// Crear el contexto
const UtilsContext = createContext<{
    mostrarAlerta: (params: AlertParams) => void;
} | undefined>(undefined);

export const UtilsProvider: React.FC<{ children: React.ReactNode}> = ({ children}) => {
    return (
        <UtilsContext.Provider
            value={
                {
                    mostrarAlerta,
                }
            }>
            {children}
            <AlertDialog />
        </UtilsContext.Provider>
    );
};

export const useUtils = () => {
    const context = useContext(UtilsContext);
    if (!context) {
        throw new Error('useUtils must be used within a UtilsProvider');
    }
    return context;
};

Creamos el componente modal llamado alertDialog.tsx

// components/AlertDialog.js
import React, { useState } from 'react';

let setOpenExt;
let setParamsExt;

const AlertDialog = () => {
    const [open, setOpen] = useState(false);
    const [props, setParams] = useState({});

    setOpenExt = setOpen;
    setParamsExt = (params) => {
        setParams(params);
    };

    const handleClose = () => {
        setOpen(false);
        if (props.onCancel) props.onCancel();
    };

    const handleAgree = () => {
        setOpen(false);
        if (props.onAccept) props.onAccept();
    };

    return open ? (
        <div className="modal-overlay">
            <div className="modal-content">
                <div className="modal-header">
                    <h3>{props.title}</h3>
                </div>
                <div className="modal-body">
                    <p>{props.content}</p>
                </div>
                <div className="modal-footer">
                    {props.showCancelButton && (
                        <button onClick={handleClose} className="modal-button cancel-button">
                            {props.cancelText || "Cancelar"}
                        </button>
                    )}
                    <button onClick={handleAgree} className="modal-button accept-button">
                        {props.acceptText || "Aceptar"}
                    </button>
                </div>
            </div>
        </div>
    ) : null;
};

export default AlertDialog;

export const mostrarAlerta = (params) => {
    setOpenExt(true);
    setParamsExt(params);
};

Estilos CSS (importarlos en app.tsx):

/* styles.css */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 9999;
}

.modal-content {
    background: white;
    padding: 20px;
    border-radius: 8px;
    max-width: 500px;
    min-width: 300px;
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.modal-header h3 {
    margin: 0;
    font-size: 1.25em;
}

.modal-body p {
    margin: 0;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}

.modal-button {
    padding: 10px 20px;
    border: none;
    font-weight: bold;
    cursor: pointer;
}

.accept-button {
    background-color: #007bff;
    color: white;
}

.cancel-button {
    background-color: #dc3545;
    color: white;
}

Paso 2: Usar el Hook en un Componente

Ahora que tienes el hook, para utilizarlo primero tenemos que envolver el componente o el arbol de componentes de la siguiente forma:

  • Vamos a app.tsx:
return (
    <ThemeProvider>
          <UtilsProvider>
              <main style={{ marginTop: "60px", paddingTop: "20px", paddingBottom: "16px" }}>
                <Component pathActual={pathActual} {...rest} />
              </main>
          </UtilsProvider>
    </ThemeProvider>
  );

En mi caso ya tenía el hook ThemeProvider.

He envuelto todo el árbol de la página con UtilsProvider.

Y ahora podremos utilizarlo de esta forma:

Solo tendríamos qué llamar al modal de la siguiente forma:

const mostrarModal = () => {
        mostrarAlerta({
            title: "Confirmación",
            content: "¿Estás seguro de que quieres continuar?",
            acceptText: "Aceptar",
            cancelText: "Cancelar",
            showCancelButton: true,
            onAccept: () => alert("Has aceptado"),
            onCancel: () => alert("Has cancelado")
        });
    };

Si nuestro hook contiene funciones qué podemos invocar tendremos que importar en nuestro componente el hook personalizado de la siguiente forma:

const {mostrarAlerta} = useUtils();

¿Por qué Crear Hooks Personalizados?

Los hooks personalizados te ayudan a encapsular lógica compleja y repetitiva, haciendo tu código más limpio y reutilizable en múltiples componentes.

Deja un comentario