React

React

Tutorial React: Introducción a los Hooks

React Introducción a los hooks en componentes funcionales. Aprende uso de useState, useEffect y más. Mejora tus habilidades en React sin componentes de clase.

¿Qué son los hooks?

Los hooks son una característica de React que permite usar el estado y otras características de React en componentes funcionales, sin necesidad de escribir una clase. Introducidos en React 16.8, los hooks permiten manejar efectos secundarios, estado y otros aspectos de los componentes de una manera más sencilla y funcional.

Reglas de los hooks

Los hooks de React siguen un conjunto de reglas estrictas que aseguran que funcionen correctamente y de manera predecible. Estas reglas son fundamentales para mantener la consistencia y evitar errores difíciles de depurar. A continuación, se detallan las reglas que deben seguirse al utilizar hooks en React.

Llamar a los hooks en el nivel superior

Los hooks deben ser llamados en el nivel superior de la función del componente. No deben ser llamados dentro de bucles, condiciones o funciones anidadas. Esta regla asegura que los hooks se llamen en el mismo orden en cada renderizado, lo cual es crucial para que React pueda mantener correctamente el estado de los hooks entre renderizados.

// Correcto
// MyComponent.jsx
import { useState, useEffect } from 'react';

export default function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar</button>
    </div>
  );
}

// Incorrecto
// MyComponent.jsx
import { useState, useEffect } from 'react';

export default function MyComponent() {
  const [count, setCount] = useState(0);

  if (count > 0) {
    useEffect(() => {
      document.title = `Count: ${count}`;
    }, [count]); // Esto romperá las reglas de los hooks
  }

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar</button>
    </div>
  );
}

Llamar a los hooks únicamente desde componentes funcionales o hooks personalizados

Los hooks solo deben ser llamados desde componentes funcionales o desde otros hooks personalizados. No deben ser llamados desde funciones regulares de JavaScript. Esta regla permite que React pueda gestionar el estado y el ciclo de vida de los hooks correctamente.

// Correcto
// useCustomHook.js
import { useState } from "react";

export function useCustomHook() {
  const [value, setValue] = useState(0);
  return [value, setValue];
}

// MyComponent.jsx
import { useCustomHook } from './hooks/useCustomHook';

export function MyComponent() {
  const [value, setValue] = useCustomHook();
  return (
    <div>
      <p>{value}</p>
      <button onClick={() => setValue(value + 1)}>Incrementar</button>
    </div>
  );
}

// Correcto
// regularFunction.js
import { useState } from "react";

export function regularFunction() {
  const [value, setValue] = useState(0); // Esto romperá las reglas de los hooks
}

Usar hooks personalizados para encapsular lógica de estado y efectos

Los hooks personalizados permiten encapsular y reutilizar lógica de estado y efectos entre múltiples componentes. Deben seguir las mismas reglas que los hooks integrados. Al crear un hook personalizado, asegúrate de que el nombre de la función comience con use para que React pueda identificarlo como un hook.

// useWindowWidth.js
import { useState, useEffect } from 'react';

export function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

// DisplayWidth.jsx
import { useWindowWidth } from "../../hooks/useWindowWidth";

export default function DisplayWidth() {
  const width = useWindowWidth();

  return <div>El ancho de la ventana es: {width}px</div>;
}

Cumplir con estas reglas es esencial para asegurar que los hooks funcionen de manera predecible y eficiente. La adherencia a estas reglas permite a React optimizar el rendimiento y la consistencia del estado en los componentes funcionales.

Vista general introductoria de todos los hooks

React proporciona una serie de hooks integrados que permiten manejar diferentes aspectos de los componentes funcionales. Cada hook tiene un propósito específico y puede ser utilizado para gestionar el estado, efectos secundarios, referencias y más.

useState: Permite añadir estado local a un componente funcional. Recibe un valor inicial y devuelve un array con dos elementos: el estado actual y una función para actualizarlo.

// Counter.jsx
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Has hecho clic {count} veces</p>
      <button onClick={() => setCount(count + 1)}>Haz clic</button>
    </div>
  );
}

useEffect: Permite ejecutar efectos secundarios en componentes funcionales. Recibe una función que se ejecutará después de que el renderizado del componente sea completado. También puede recibir un array de dependencias que determina cuándo se debe volver a ejecutar el efecto.

// Timer.jsx
import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <div>Segundos: {count}</div>;
}

useContext: Permite acceder al contexto de React. Es útil para compartir datos que deben ser accesibles por muchos componentes sin necesidad de pasar props manualmente por cada nivel del árbol de componentes.

// ThemeContext.jsx
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

export function ThemeProvider({ children }) {
  return (
    <ThemeContext.Provider value="dark">
      {children}
    </ThemeContext.Provider>
  );
}

export function ThemedComponent() {
  const theme = useContext(ThemeContext);
  return <div>El tema actual es {theme}</div>;
}

// App.jsx
import { ThemedComponent, ThemeProvider } from './components/ThemedComponent/ThemeContext';

export default function App() {
  return (
    <ThemeProvider>
      <ThemedComponent />
    </ThemeProvider>
  );
}

useReducer: Similar a useState, pero más adecuado para gestionar estados complejos que involucran múltiples subvalores. Se usa con un reducer, una función pura que toma el estado actual y una acción, y devuelve un nuevo estado.

// CounterReducer.jsx
import { useReducer } from 'react';

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();
  }
}

export default function CounterReducer() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Contador: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Incrementar</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrementar</button>
    </div>
  );
}

useCallback: Memoriza funciones para evitar recrearlas en cada renderizado, lo cual es útil para optimizar el rendimiento de componentes que dependen de funciones que se pasan como props.

// CallbackComponent.jsx
import { useState, useCallback } from 'react';

export default function CallbackComponent() {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Contador: {count}</p>
      <button onClick={increment}>Incrementar</button>
    </div>
  );
}

useMemo: Memoriza valores calculados para evitar recalculaciones en cada renderizado. Recibe una función y un array de dependencias.

// ExpensiveCalculation.jsx
import { useState, useMemo } from 'react';

const expensiveCalculation = (num) => {
  console.log('Calculando...');
  for (let i = 0; i < 1000000000; i++) { } // Simulación de cálculo costoso
  return num * 2;
};

export default function ExpensiveCalculation() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const memoizedValue = useMemo(() => expensiveCalculation(count), [count]);

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Escribe algo"
      />
      <p>Texto: {text}</p>
      <p>Contador: {count}</p>
      <p>Resultado del cálculo: {memoizedValue}</p>
      <button onClick={() => setCount(count + 1)}>Incrementar contador</button>
    </div>
  );
}

useRef: Permite crear una referencia mutable que no provoca un nuevo renderizado cuando cambia. Es útil para acceder y manipular elementos del DOM directamente. A diferencia de useState, los cambios en useRef no afectan la salida visual del componente.

// FocusInput.jsx
import { useRef } from 'react';

export default function FocusInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Enfocar Input</button>
    </div>
  );
}

useLayoutEffect: Similar a useEffect, pero se ejecuta de manera sincrónica después de todas las mutaciones del DOM. Generalmente se usa para leer el layout del DOM y sincronizarlo antes de que se pinte en la pantalla.

// LayoutEffectComponent.jsx
import { useState, useLayoutEffect, useRef } from 'react';

export default function LayoutEffectComponent() {
  const [width, setWidth] = useState(0);
  const divRef = useRef(null);

  useLayoutEffect(() => {
    setWidth(divRef.current.offsetWidth);
  }, []);

  return (
    <div ref={divRef}>
      <p>El ancho del div es: {width}</p>
    </div>
  );
}

Advertencia: El código dentro de useLayoutEffect y todas las actualizaciones de estado programadas desde él bloquean el navegador de volver a pintar en la pantalla. Cuando es usado excesivamente, puede hacer tu aplicación muy lenta. Cuando sea posible se prefiere usar useEffect.

Estos hooks proporcionan una manera flexible y eficiente de manejar el estado y los efectos secundarios en componentes funcionales de React, permitiendo una mayor modularidad y reutilización de la lógica de los componentes.

Limitaciones de los componentes de clase

Los componentes de clase en React han sido una herramienta fundamental desde los inicios de la biblioteca, pero presentan varias limitaciones que han llevado a la introducción y adopción de los hooks en componentes funcionales. A continuación, se exploran algunas de estas limitaciones:

Complejidad en la gestión del estado y efectos secundarios

En los componentes de clase, la gestión del estado y los efectos secundarios puede volverse complicada y difícil de seguir. Los métodos de ciclo de vida (componentDidMount, componentDidUpdate, componentWillUnmount) a menudo contienen código mezclado que maneja diferentes aspectos del estado y los efectos secundarios, lo que puede hacer que el código sea menos legible y más propenso a errores.

// ClaseComponent.jsx
import { Component } from 'react';

export default class ClaseComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
      width: window.innerWidth
    };
  }

  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
    window.addEventListener('resize', this.handleResize);
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      document.title = `Count: ${this.state.count}`;
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }

  handleResize = () => {
    this.setState({ width: window.innerWidth });
  };

  incrementCount = () => {
    this.setState((prevState) => ({ count: prevState.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.incrementCount}>Increment</button>
        <p>Window width: {this.state.width}</p>
      </div>
    );
  }
}

Reutilización de lógica de estado

Reutilizar la lógica de estado y efectos secundarios en componentes de clase es complicado y generalmente requiere patrones como componentes de orden superior (Higher-Order Components, HOCs) o render props. Estos patrones pueden aumentar la complejidad y el número de capas en la jerarquía de componentes, haciendo que el código sea más difícil de seguir y mantener.

// ClaseComponent.jsx
import { Component } from 'react';

export default class ClaseComponent extends Component {
  render() {
    const { width } = this.props;
    return (
      <div>
        <p>Width: {width}</p>
      </div>
    )
  }
}

// withWindowWidth.jsx (HOC)
import { Component } from 'react';

export function withWindowWidth(WrappedComponent) {
  return class extends Component {
    state = { width: window.innerWidth };

    handleResize = () => {
      this.setState({ width: window.innerWidth });
    };

    componentDidMount() {
      window.addEventListener('resize', this.handleResize);
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.handleResize);
    }

    render() {
      const { width } = this.state;
      return <WrappedComponent width={width} {...this.props} />;
    }
  };
}

// App.jsx
import ClaseComponent from './components/ClaseComponent/ClaseComponent';
import { withWindowWidth } from './components/withWindowWidth/withWindowWidth';

const HofComponent = withWindowWidth(ClaseComponent);

export default function App() {
  return (
    <HofComponent />
  );
}

Dificultad para dividir la lógica por funcionalidad

En componentes de clase, la lógica se agrupa por métodos de ciclo de vida en lugar de por funcionalidad. Esto puede hacer que la lógica relacionada, como la configuración de un suscriptor y su limpieza, esté dispersa en varios métodos, dificultando la comprensión y el mantenimiento del código.

// ClaseComponent.jsx
import { Component } from 'react';

export default class ClaseComponent extends Component {
  state = { count: 0 };

  componentDidMount() {
    this.timer = setInterval(() => this.setState({ count: this.state.count + 1 }), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {
    return <div>Count: {this.state.count}</div>;
  }
}

Problemas de rendimiento y optimización

Los componentes de clase pueden tener problemas de rendimiento debido a la recreación de funciones en cada renderizado. Aunque se pueden utilizar métodos como shouldComponentUpdate y PureComponent para optimizar, esto añade complejidad al código y no siempre es intuitivo.

// ClaseComponent.jsx
import { PureComponent } from 'react';

export default class ClaseComponent extends PureComponent {
  state = { count: 0 };

  incrementCount = () => {
    this.setState((prevState) => ({ count: prevState.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

Dificultad en la prueba y depuración

Los componentes de clase pueden ser más difíciles de probar y depurar debido a la necesidad de simular el ciclo de vida del componente y el estado. Los hooks permiten una manera más directa y funcional de manejar el estado y los efectos, lo que facilita la prueba y depuración del código.

Estas limitaciones de los componentes de clase han llevado a la adopción de hooks en componentes funcionales, proporcionando una forma más sencilla y modular de manejar el estado y los efectos secundarios en React.

Certifícate en React con CertiDevs PLUS

Ejercicios de esta lección Introducción a los Hooks

Evalúa tus conocimientos de esta lección Introducción a los Hooks con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Todas las lecciones de React

Accede a todas las lecciones de React y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A React Y Su Ecosistema

React

Introducción Y Entorno

Instalar React Y Crear Nuevo Proyecto

React

Introducción Y Entorno

Introducción A Jsx

React

Componentes

Introducción A Componentes

React

Componentes

Componentes Funcionales

React

Componentes

Eventos En React

React

Componentes

Props Y Manejo De Datos Entre Componentes

React

Componentes

Renderizado Condicional

React

Componentes

Renderizado Iterativo Con Bucles

React

Componentes

Manejo De Clases Y Estilos

React

Componentes

Introducción A Los Hooks

React

Hooks

Estado Y Ciclo De Vida De Los Componentes

React

Hooks

Hooks Para Manejo De Estado Y Efectos Secundarios

React

Hooks

Hooks Para Gestión De Estado Complejo Y Contexto

React

Hooks

Hooks Para Optimización Y Actualizaciones Concurrentes

React

Hooks

Introducción A React Router

React

Navegación Y Enrutamiento

Definición Y Manejo De Rutas

React

Navegación Y Enrutamiento

Rutas Anidadas Y Rutas Dinámicas

React

Navegación Y Enrutamiento

Navegación Programática Y Redireccionamiento

React

Navegación Y Enrutamiento

Nuevos Métodos Create De React Router

React

Navegación Y Enrutamiento

Solicitudes Http Con Fetch Api

React

Interacción Http Con Backend

Solicitudes Http Con Axios

React

Interacción Http Con Backend

Estado Local Con Usestate Y Usereducer

React

Servicios Y Gestión De Estado

Estado Global Con Context Api

React

Servicios Y Gestión De Estado

Estado Global Con Redux Toolkit

React

Servicios Y Gestión De Estado

Custom Hooks Para Servicios Compartidos

React

Servicios Y Gestión De Estado

Certificados de superación de React

Supera todos los ejercicios de programación del curso de React y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué son los hooks y cómo funcionan en React.
  • Aprender las reglas fundamentales para usar hooks de manera correcta.
  • Implementar useState para añadir estado a los componentes funcionales.
  • Utilizar useEffect para manejar efectos secundarios en componentes.
  • Desarrollar hooks personalizados para encapsular lógica reutilizable.
  • Reconocer las limitaciones de los componentes de clase y cómo los hooks las superan.
  • Aplicar otros hooks como useContext, useReducer, useCallback, useMemo y useRef en proyectos prácticos.