useToggle — Toggle booleano simple con controles on, off y toggle
Receta
import { useState, useCallback, useMemo } from "react";
interface UseToggleReturn {
/** Valor booleano actual */
value: boolean;
/** Invierte el valor */
toggle: () => void;
/** Establece a true */
on: () => void;
/** Establece a false */
off: () => void;
/** Establece a un valor específico */
set: (value: boolean) => void;
}
function useToggle(initialValue: boolean = false): UseToggleReturn {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue((v) => !v), []);
const on = useCallback(() => setValue(true), []);
const off = useCallback(() => setValue(false), []);
return useMemo(
() => ({ value, toggle, on, off, set: setValue }),
[value, toggle, on, off]
);
}Cuándo usarlo: Tienes un estado booleano (modal abierto, sidebar visible, dark mode, feature flag) y quieres una API más limpia que useState crudo con alternancia de !prev dispersa en tu JSX.
Ejemplo Trabajando
"use client";
function Sidebar() {
const sidebar = useToggle(false);
return (
<div style={{ display: "flex" }}>
<aside
style={{
width: sidebar.value ? 250 : 0,
overflow: "hidden",
transition: "width 0.3s",
background: "#f5f5f5",
borderRight: "1px solid #e0e0e0",
}}
>
<nav style={{ padding: 16, whiteSpace: "nowrap" }}>
<ul style={{ listStyle: "none", padding: 0 }}>
<li>Panel de Control</li>
<li>Configuración</li>
<li>Perfil</li>
</ul>
</nav>
</aside>
<main style={{ flex: 1, padding: 16 }}>
<button onClick={sidebar.toggle}>
{sidebar.value ? "Cerrar" : "Abrir"} Sidebar
</button>
<p>Contenido principal aquí</p>
</main>
</div>
);
}
function DarkModeToggle() {
const darkMode = useToggle(false);
return (
<div
style={{
background: darkMode.value ? "#1a1a2e" : "#ffffff",
color: darkMode.value ? "#e0e0e0" : "#333",
padding: 24,
transition: "all 0.3s",
}}
>
<label style={{ display: "flex", alignItems: "center", gap: 8 }}>
<input
type="checkbox"
checked={darkMode.value}
onChange={darkMode.toggle}
/>
Modo Oscuro
</label>
</div>
);
}
function FeatureFlags() {
const betaFeature = useToggle(false);
const notifications = useToggle(true);
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<label>
<input
type="checkbox"
checked={betaFeature.value}
onChange={betaFeature.toggle}
/>
Características Beta
</label>
<label>
<input
type="checkbox"
checked={notifications.value}
onChange={notifications.toggle}
/>
Notificaciones
</label>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={notifications.on}>Habilitar Todo</button>
<button onClick={notifications.off}>Deshabilitar Todo</button>
</div>
</div>
);
}Lo que esto demuestra:
sidebar.togglereemplaza() => setOpen(!open)con una API más limpiadarkMode.valuese lee naturalmente en expresiones de templatenotifications.onynotifications.offproporcionan llamadas explícitas para establecer verdadero/falso- Los múltiples toggles son independientes y pueden componerse libremente
Profundización
Cómo Funciona
toggleusa una actualización funcional(v) => !vpara siempre invertir basándose en el valor actual, evitando problemas de cierre obsoleto.onyoffestablecen valores absolutos, útiles para botones de reinicio o manejadores de eventos que siempre deben establecer un state específico.useCallbackmemoiza todas las funciones para que sean referencialmente estables entre renderizados, seguras para pasar como props o usar en arrays de dependencias.useMemoenvuelve el objeto de retorno para que los consumidores obtengan una referencia estable cuando solo se usan las funciones (que ya son estables).
Parámetros y Valores de Retorno
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
initialValue | boolean | false | Valor inicial |
| Retorno | Tipo | Descripción |
|---|---|---|
value | boolean | State actual |
toggle | () => void | Invierte el valor |
on | () => void | Establece a true |
off | () => void | Establece a false |
set | (value: boolean) => void | Establece a cualquier booleano |
Variaciones
Retorno de tupla: Para una API más similar a useState, retorna una tupla:
function useToggle(initial = false): [boolean, () => void] {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
return [value, toggle];
}
// Uso
const [isOpen, toggleOpen] = useToggle();Con reducer: Para lógica de toggle compleja con historial de acciones:
function useToggle(initial = false) {
const [value, dispatch] = useReducer(
(state: boolean, action: "toggle" | "on" | "off") => {
switch (action) {
case "toggle": return !state;
case "on": return true;
case "off": return false;
}
},
initial
);
return { value, dispatch };
}Toggle persistente: Combina con useLocalStorage para toggles que sobreviven a la actualización:
function usePersistentToggle(key: string, initial = false) {
const [value, setValue] = useLocalStorage(key, initial);
const toggle = useCallback(() => setValue((v) => !v), [setValue]);
const on = useCallback(() => setValue(true), [setValue]);
const off = useCallback(() => setValue(false), [setValue]);
return { value, toggle, on, off };
}Notas de TypeScript
- El tipo de retorno es una interfaz nombrada, lo que facilita pasar el objeto toggle como un prop.
- No se necesitan genéricos ya que el valor siempre es
boolean. - La función
setaceptabooleandirectamente (no una unión con una función actualizadora) para mantener la API simple.
Trampa
- Exceso de ingeniería — Para un único toggle usado una vez,
useState(false)simple está bien. Solución: Recurre auseTogglecuando tengas múltiples toggles o quieras la API semánticaon/off/toggle. - Identidad del objeto — Sin
useMemo, el objeto retornado crea una nueva referencia en cada renderizado, lo que puede causar re-renderizados innecesarios en los hijos. Solución: EluseMemoen la receta maneja esto. - Controlado vs no controlado — Este hook es dueño de su propio state. Si el padre necesita controlar el valor, pasa el state hacia abajo como props en su lugar. Solución: Usa
useTogglepara el state local de la UI, no para el state compartido.
Alternativas
| Paquete | Nombre del Hook | Notas |
|---|---|---|
usehooks-ts | useToggle | Retorno de tupla [value, toggle] |
@uidotdev/usehooks | useToggle | Mínimo, retorno de tupla |
ahooks | useToggle | Admite toggle no booleano entre dos valores |
react-use | useToggle | Toggle booleano simple |
Preguntas Frecuentes
¿Por qué usar useToggle en lugar de useState(false) simple?
useToggleproporciona una API semántica:toggle(),on(),off()yset().- Elimina alternancia de
!prevdispersa en JSX. - Es más útil cuando tienes múltiples toggles o quieres una intención más clara en los manejadores de eventos.
¿Cuál es la diferencia entre toggle(), on(), off() y set()?
toggle()invierte el valor actual a su opuesto.on()siempre establece el valor atrue.off()siempre establece el valor afalse.set(value)establece el valor a cualquier booleano que pases.
¿Por qué el hook envuelve el objeto de retorno en useMemo?
Sin useMemo, se crea una nueva referencia de objeto en cada renderizado. Esto puede causar re-renderizados innecesarios en componentes hijos que reciben el objeto toggle como un prop. useMemo mantiene la referencia estable cuando solo se usan las funciones (ya memoizadas).
¿Por qué toggle() usa una actualización funcional (v) => !v en lugar de !value?
La actualización funcional lee el state actual en el momento de la ejecución, no en el momento en que se creó la función. Esto evita errores de cierre obsoleto cuando se llama a toggle() desde manejadores de eventos o efectos.
¿Puedo usar el patrón de retorno de tupla en lugar del patrón de objeto?
Sí. La sección Variaciones muestra una variante de tupla:
const [isOpen, toggleOpen] = useToggle();Usa la tupla para casos simples y el objeto para múltiples controles (on, off, set).
¿Cómo hago que un toggle persista en las actualizaciones de página?
Combina con useLocalStorage:
function usePersistentToggle(key: string, initial = false) {
const [value, setValue] = useLocalStorage(key, initial);
const toggle = useCallback(() => setValue((v) => !v), [setValue]);
return { value, toggle };
}Trampa: Pasé el objeto toggle como un prop y mi hijo se renderiza cada vez. ¿Por qué?
Si destructuras y recreas el objeto en el padre, la referencia cambia. Pasa el objeto envuelto en useMemo directamente, o pasa funciones estables individuales (toggle, on, off) como props separados.
Trampa: Usé useToggle para el state que el padre también necesita controlar. ¿Es correcto?
No. useToggle es dueño de su propio state (no controlado). Si el padre necesita controlar el valor, pasa el booleano como un prop y usa callbacks. Usa useToggle solo para el state local de la UI.
¿Cuál es el tipo de retorno de TypeScript de useToggle?
Retorna UseToggleReturn, una interfaz con value: boolean, toggle: () => void, on: () => void, off: () => void y set: (value: boolean) => void. No se necesitan genéricos.
¿Puedo usar useReducer en lugar de useState para el toggle?
Sí. La sección Variaciones muestra una variante basada en reducer que despacha acciones de cadena como "toggle", "on" y "off". Esto es útil para lógica de toggle compleja o seguimiento del historial de acciones.
Relacionado
- useLocalStorage — persistir state del toggle
- useKeyboardShortcut — toggle con una tecla de acceso rápido
- useClickOutside — cerrar al hacer clic fuera