Saltar al contenido
home / blog / react-hooks-patrones
React Hooks JavaScript

Patrones avanzados con React Hooks

2026-04-16 · 11 min de lectura
Patrones avanzados con React Hooks

Custom hooks, composición de hooks, y patrones como useReducer con Context para manejar estado complejo sin Redux.

Custom Hooks: extrayendo lógica reutilizable

La regla de oro: si tienes lógica con estado que aparece en más de un componente, es candidata a ser un custom hook.

useFetch — el clásico

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(r => r.json())
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// Uso:
function UserProfile({ id }: { id: string }) {
  const { data, loading, error } = useFetch<User>(`/api/users/${id}`);
  if (loading) return <Spinner />;
  if (error) return <Error />;
  return <Profile user={data!} />;
}

useLocalStorage

function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initial;
    } catch {
      return initial;
    }
  });

  const set = useCallback((val: T | ((prev: T) => T)) => {
    setValue(prev => {
      const next = typeof val === 'function' ? (val as (p: T) => T)(prev) : val;
      localStorage.setItem(key, JSON.stringify(next));
      return next;
    });
  }, [key]);

  return [value, set] as const;
}

useReducer + Context: estado global sin Redux

Para estado global moderadamente complejo, useReducer con Context es suficiente y no requiere dependencias externas.

// store/theme.tsx
type Theme = 'dark' | 'light';
type Action = { type: 'TOGGLE_THEME' } | { type: 'SET_THEME'; payload: Theme };

function reducer(state: Theme, action: Action): Theme {
  switch (action.type) {
    case 'TOGGLE_THEME': return state === 'dark' ? 'light' : 'dark';
    case 'SET_THEME': return action.payload;
    default: return state;
  }
}

const ThemeContext = createContext<{
  theme: Theme;
  dispatch: Dispatch<Action>;
} | null>(null);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, dispatch] = useReducer(reducer, 'dark');
  return (
    <ThemeContext.Provider value={{ theme, dispatch }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be inside ThemeProvider');
  return ctx;
}

Composición de hooks

Los hooks se componen naturalmente — puedes construir hooks complejos a partir de simples:

function useUserData(userId: string) {
  const { data: user, loading: userLoading } = useFetch<User>(`/api/users/${userId}`);
  const { data: posts, loading: postsLoading } = useFetch<Post[]>(`/api/users/${userId}/posts`);

  return {
    user,
    posts,
    loading: userLoading || postsLoading,
  };
}

useCallback y useMemo — cuándo usarlos

La regla: úsalos cuando el costo de recomputar es mayor que el costo de comparar.

// ✅ Sí usar useMemo — cálculo costoso
const sortedItems = useMemo(
  () => items.sort((a, b) => b.score - a.score),
  [items]
);

// ✅ Sí usar useCallback — función pasada a componente memoizado
const handleSubmit = useCallback(
  (data: FormData) => {
    mutate(data);
  },
  [mutate]
);

// ❌ No necesario — el cálculo es trivial
const doubled = useMemo(() => count * 2, [count]);

useRef más allá de los DOM refs

useRef almacena valores mutables que no deben triggear re-renders:

function useInterval(callback: () => void, delay: number) {
  const savedCallback = useRef(callback);

  // Actualizar la referencia sin re-render
  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

Conclusión

Los hooks cambian la forma de pensar en React: en lugar de “qué lifecycle method uso aquí”, piensas en “qué efecto tiene este valor”. La composición es el superpoder real — hooks pequeños y enfocados que se combinan en comportamientos complejos.

El patrón useReducer + Context cubre el 80% de los casos donde antes hubieras alcanzado para Redux. Empieza simple y añade complejidad solo cuando la necesites.