Sobes.tech
Junior

¿Cuáles son los hooks en React?

sobes.tech AI

Respuesta de la IA

En React, a partir de la versión 16.8, los hooks permiten a los componentes funcionales gestionar el estado y usar ciclos de vida, que anteriormente estaban disponibles solo para componentes de clase.

Aquí están los hooks integrados principales:

  • useState: Permite agregar estado interno a un componente funcional. Devuelve una pareja: el estado actual y una función para actualizarlo.

    // Ejemplo useState
    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // Inicialización del estado con 0
    
      return (
        <div>
          <p>Has presionado {count} veces</p>
          <button onClick={() => setCount(count + 1)}>
            Presióname
          </button>
        </div>
      );
    }
    
  • useEffect: Permite realizar efectos secundarios en componentes funcionales. Similar a los métodos del ciclo de vida de componentes de clase (componentDidMount, componentDidUpdate, componentWillUnmount).

    // Ejemplo useEffect
    import React, { useState, useEffect } from 'react';
    
    function Example() {
      const [count, setCount] = useState(0);
    
      // Se ejecuta después de cada renderizado
      useEffect(() => {
        document.title = `Has presionado ${count} veces`;
      });
    
      // Solo se ejecuta en el primer renderizado (matriz de dependencias vacía)
      useEffect(() => {
        console.log('Componente montado');
        return () => {
          console.log('Componente desmontado'); // Función de limpieza
        };
      }, []);
    
      // Se ejecuta al cambiar count
      useEffect(() => {
        console.log('Count cambió:', count);
      }, [count]);
    
      return (
        <div>
          <p>Has presionado {count} veces</p>
          <button onClick={() => setCount(count + 1)}>
            Presióname
          </button>
        </div>
      );
    }
    
  • useContext: Permite suscribirse a los cambios del contexto de React. Toma un objeto de contexto creado con React.createContext().

    // Ejemplo useContext
    import React, { useContext } from 'react';
    
    const ThemeContext = React.createContext('light'); // Creación del contexto
    
    function ThemedButton() {
      const theme = useContext(ThemeContext); // Uso del contexto
    
      return (
        <button className={theme}>
          Botón en tema {theme}
        </button>
      );
    }
    
    function App() {
      return (
        <ThemedButton />
      );
    }
    
  • useReducer: Alternativa a useState para gestionar estados más complejos, que incluyen múltiples subvalores o cuando el siguiente estado depende del anterior. Similar a los reductores en Redux.

    // Ejemplo useReducer
    import React, { useReducer } from 'react';
    
    const initialState = { count: 0 };
    
    function reducer(state, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        case 'decrement':
          return { count: state.count - 1 };
        default:
          throw new Error();
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
    
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: 'increment' })}>+</button>
          <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </>
      );
    }
    
  • useCallback: Memoiza una función de callback. Devuelve una versión memorizada del callback que solo cambia si cambia alguna de las dependencias. Es útil para evitar renderizados innecesarios de componentes hijos al pasar callbacks.

    // Ejemplo useCallback
    import React, { useState, useCallback } from 'react';
    
    function Parent({ addTodo }) {
      const [count, setCount] = useState(0);
    
      const handleAddTodo = useCallback(() => {
        addTodo();
      }, [addTodo]); // Dependencia de addTodo
    
      return (
        <>
          <button onClick={() => setCount(count + 1)}>
            Incrementar contador
          </button>
          <Child onAddItem={handleAddTodo} />
        </>
      );
    }
    
    function Child({ onAddItem }) {
      console.log('Child renderiza'); // Solo renderiza cuando onAddItem cambia
      return <button onClick={onAddItem}>Agregar elemento</button>;
    }
    
  • useMemo: Memoiza un valor calculado. Devuelve un valor memorizado que solo se recalcula si cambia alguna de las dependencias. Útil para mejorar el rendimiento en cálculos costosos.

    // Ejemplo useMemo
    import React, { useMemo, useState } from 'react';
    
    function expensiveCalculation(num) {
      console.log('Ejecutando cálculo costoso...');
      for (let i = 0; i < 1000000; i++) {
        num += 1;
      }
      return num;
    }
    
    function Example() {
      const [count, setCount] = useState(0);
      const [todos, setTodos] = useState([]);
    
      const calculation = useMemo(() => expensiveCalculation(count), [count]); // Dependencia de count
    
      const addTodo = () => {
        setTodos(t => [...t, "Nuevo Todo"]);
      };
    
      return (
        <div>
          <div>
            <h2>Mis Todo</h2>
            {todos.map((todo, index) => (
              <p key={index}>{todo}</p>
            ))}
            <button onClick={addTodo}>Agregar Todo</button>
          </div>
          <hr />
          <div>
            Contador: {count}
            <button onClick={() => setCount(c => c + 1)}>+</button>
            <h2>Cálculo costoso</h2>
            {calculation}
          </div>
        </div>
      );
    }
    
  • useRef: Permite crear una referencia mutable que persiste durante toda la vida del componente. Se usa comúnmente para acceder a elementos DOM o almacenar cualquier valor mutable que no cause renderizado al cambiar.

    // Ejemplo useRef
    import React, { useRef } from 'react';
    
    function TextInputWithFocusButton() {
      const inputEl = useRef(null); // Crear referencia
    
      const onButtonClick = () => {
        // current apunta al elemento DOM montado
        inputEl.current.focus();
      };
    
      return (
        <>
          <input ref={inputEl} type="text" /> // Asignar referencia
          <button onClick={onButtonClick}>Enfocar campo de entrada</button>
        </>
      );
    }
    
  • useImperativeHandle: Configura el descriptor del instancia que se expone al componente padre cuando se usa ref. Permite que el componente hijo exponga ciertos métodos al componente padre. Se usa junto con forwardRef.

    // Ejemplo useImperativeHandle
    import React, { useRef, useImperativeHandle, forwardRef } from 'react';
    
    const MyInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    
      useImperativeHandle(ref, () => ({
        focusInput: () => {
          inputRef.current.focus();
        }
      }));
    
      return <input ref={inputRef} {...props} />;
    });
    
    function ParentComponent() {
      const inputRef = useRef();
    
      const handleFocus = () => {
        inputRef.current.focusInput(); // Llamada a método del componente hijo
      };
    
      return (
        <div>
          <MyInput ref={inputRef} />
          <button onClick={handleFocus}>Enfocar desde Parent</button>
        </div>
      );
    }
    
  • useLayoutEffect: Igual que useEffect, pero se ejecuta sincrónicamente después de todas las mutaciones del DOM. Útil para leer datos del DOM o hacer mutaciones que deben ocurrir antes de que el navegador repinte la pantalla. Puede bloquear el renderizado.

    // Ejemplo useLayoutEffect
    import React, { useLayoutEffect, useRef } from 'react';
    
    function Tooltip({ children, text }) {
      const ref = useRef(null);
    
      useLayoutEffect(() => {
        if (ref.current) {
          // Cálculos basados en el DOM antes del repintado
          const { top, height } = ref.current.getBoundingClientRect();
          console.log(`El elemento está en ${top} con altura ${height}`);
        }
      }, [children]);
    
      return (
        <span ref={ref} title={text}>
          {children}
        </span>
      );
    }
    
  • useDebugValue: Muestra una etiqueta personalizada para los hooks en React DevTools. Se usa en hooks personalizados para depuración.

    // Ejemplo useDebugValue
    import React, { useState, useDebugValue } from 'react';
    
    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // Muestra 'Friend Status: Offline' o 'Friend Status: Online' en DevTools
      useDebugValue(isOnline === true ? 'Online' : 'Offline');
    
      // ... lógica para obtener el estado del amigo
    
      return isOnline;
    }
    
    function FriendListItem({ friend }) {
      const isOnline = useFriendStatus(friend.id);
    
      return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
          {friend.name} - {isOnline ? 'En línea' : 'Desconectado'}
        </li>
      );
    }
    

Además de los hooks integrados, existen hooks personalizados (Custom Hooks). Son funciones JavaScript que comienzan con "use". Permiten reutilizar lógica de estado entre componentes. Un hook personalizado puede llamar a otros hooks.

// Ejemplo de hook personalizado
import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState([window.innerWidth, window.innerHeight]);

  useEffect(() => {
    const handleResize = () => {
      setSize([window.innerWidth, window.innerHeight]);
    };
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return size;
}

function MyComponent() {
  const [width, height] = useWindowSize(); // Uso del hook personalizado

  return (
    <p>Dimensiones de la ventana: {width}x{height}</p>
  );
}

Es importante seguir las Reglas de los Hooks:

  1. Llamar a los hooks solo en el nivel superior de componentes React o hooks personalizados. No llamarlos dentro de bucles, condiciones o funciones anidadas.
  2. Llamar a los hooks solo desde componentes funcionales de React o hooks personalizados. No llamarlos desde funciones JavaScript normales o métodos de componentes de clase.