Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
import { useState, useCallback, useRef } from "react";
interface UseCopyToClipboardReturn {
/** El texto copiado más recientemente, o null */
copiedText: string | null;
/** Si el texto fue copiado recientemente (se reinicia después del timeout) */
isCopied: boolean;
/** Copia el texto dado al portapapeles */
copy: (text: string) => Promise<boolean>;
/** Reinicia el estado copiado manualmente */
reset: () => void;
}
function useCopyToClipboard(
resetDelay: number = 2000
): UseCopyToClipboardReturn {
const [copiedText, setCopiedText] = useState<string | null>(null);
const [isCopied, setIsCopied] = useState(false);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const reset = useCallback(() => {
setCopiedText(null);
setIsCopied(false);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
}, []);
const copy = useCallback(
async (text: string): Promise<boolean> => {
// Intenta primero la API Clipboard moderna
if (navigator?.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
setCopiedText(text);
setIsCopied(true);
// Reinicio automático después del retraso
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setIsCopied(false);
timerRef.current = null;
}, resetDelay);
return true;
} catch {
// Falló la API Clipboard (p. ej., permisos denegados)
}
}
// Alternativa: execCommand para navegadores más antiguos
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-9999px";
textarea.style.top = "-9999px";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const success = document.execCommand("copy");
document.body.removeChild(textarea);
if (success) {
setCopiedText(text);
setIsCopied(true);
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setIsCopied(false);
timerRef.current = null;
}, resetDelay);
}
return success;
} catch {
return false;
}
},
[resetDelay]
);
return { copiedText, isCopied, copy, reset };
}Cuándo usarlo: Tienes un botón "Copiar" junto a fragmentos de código, claves API, URLs o enlaces de compartir y deseas retroalimentación visual cuando la copia tiene éxito.
"use client";
function CodeBlock({ code }: { code: string }) {
const { isCopied, copy } = useCopyToClipboard(3000);
return (
<div style={{ position: "relative", background: "#1e1e1e", padding: 16, borderRadius: 8 }}>
<pre style={{ color: "#d4d4d4", margin: 0 }}>
<code>{code}</code>
</pre>
<button
onClick={() => copy(code)}
style={{
position: "absolute",
top: 8,
right: 8,
padding: "4px 12px",
background: isCopied ? "#22c55e" : "#3b82f6",
color: "#fff",
border: "none",
borderRadius: 4,
cursor: "pointer",
transition: "background 0.2s",
}}
>
{isCopied ? "¡Copiado!" : "Copiar"}
</button>
</div>
);
}
function ShareLink({ url }: { url: string }) {
const { isCopied, copy } = useCopyToClipboard();
return (
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<input value={url} readOnly style={{ flex: 1, padding: 8 }} />
<button onClick={() => copy(url)}>
{isCopied ? "¡Enlace copiado!" : "Compartir"}
</button>
</div>
);
}Qué demuestra esto:
isCopied se reinicia automáticamente después de 3 segundos (o 2 segundos por defecto)copy asincrónica devuelve un boolean para que los llamadores verifiquen el éxitodocument.execCommand para navegadores sin la API Clipboardnavigator.clipboard.writeText es la API moderna basada en promesas. Requiere un contexto seguro (HTTPS) y puede solicitar permiso.textarea fuera de pantalla, selecciona su contenido y ejecuta el comando copy. Esto está obsoleto pero ampliamente soportado.isCopied cambia a true y se reinicia automáticamente después de resetDelay ms. Esto proporciona retroalimentación "¡Copiado!" sin limpieza manual.| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
resetDelay | number | 2000 | Milisegundos antes de que isCopied se reinicie a false |
| Retorno | Tipo | Descripción |
|---|---|---|
copiedText | string o null | El último texto copiado exitosamente |
isCopied | boolean | Si una copia reciente fue exitosa (se reinicia automáticamente) |
copy | (text: string) => Promise<boolean> | Desencadena copia, devuelve éxito |
reset | () => void | Reinicia manualmente el estado |
Copiar texto enriquecido (HTML): Usa navigator.clipboard.write con un ClipboardItem para contenido formateado:
const blob = new Blob([htmlString], { type: "text/html" });
const item = new ClipboardItem({ "text/html": blob });
await navigator.clipboard.write([item]);Copiar desde un elemento: Acepta una ref en lugar de una cadena y lee innerText:
const copyFromRef = async (ref: React.RefObject<HTMLElement>) => {
const text = ref.current?.innerText ?? "";
return copy(text);
};copy devuelve Promise<boolean> para que los llamadores puedan await y reaccionar al fracaso.copy desde un manejador de eventos, no desde un temporizador o efecto.false.document.execCommand("copy") está obsoleto y puede ser eliminado. Solución: La API Clipboard es la ruta principal; la alternativa es una red de seguridad para navegadores heredados.| Paquete | Nombre del Hook | Notas |
|---|---|---|
usehooks-ts | useCopyToClipboard | API similar, sin alternativa |
@uidotdev/usehooks | useCopyToClipboard | Mínimo, solo API Clipboard |
react-use | useCopyToClipboard | Incluye estado de error |
copy-to-clipboard (npm) | copy() | No es un hook; función de utilidad con alternativa |
navigator.clipboard.writeText es la API moderna basada en promesas y es el enfoque preferido.document.execCommand("copy") está obsoleto pero tiene un soporte más amplio en navegadores más antiguos.isCopied cambia a true inmediatamente después de una copia exitosa.false después de resetDelay milisegundos (por defecto: 2000).Sí. copy devuelve Promise<boolean>:
const success = await copy("some text");
if (!success) {
showErrorToast("Copy failed");
}reset() borra manualmente copiedText, establece isCopied a false y cancela cualquier temporizador de reinicio automático pendiente. Úsalo cuando necesites reiniciar el estado antes de que se active el reinicio automático (p. ej., al cerrar un modal).
La API Clipboard requiere un contexto seguro (HTTPS o localhost). En HTTP simple, navigator.clipboard es undefined. El hook recurre a execCommand, pero prueba ambas rutas para estar seguro.
Los navegadores requieren que el acceso al portapapeles ocurra en respuesta a un gesto del usuario (clic, pulsación de tecla). Llamar a copy() desde un temporizador, efecto o callback asincrónico sin una acción de usuario previa será bloqueado. Siempre desencadénala desde un manejador de eventos.
<textarea> fuera de pantalla y se añade al DOM.select() y execCommand("copy").Usa navigator.clipboard.write con un ClipboardItem:
const blob = new Blob([htmlString], { type: "text/html" });
const item = new ClipboardItem({ "text/html": blob });
await navigator.clipboard.write([item]);La interfaz UseCopyToClipboardReturn nombrada proporciona documentación clara, autocompletado de IDE y puede ser reutilizada si los consumidores necesitan tipificar props que aceptan el valor de retorno del hook.
copy se tipifica como (text: string) => Promise<boolean>. La entrada siempre es una cadena y el retorno indica éxito o fracaso. No se necesitan genéricos.
Revisado por Chris St. John·Última actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥