Preguntas frecuentes sobre Hooks

Los Hooks son una adición nueva en React 16.8. Te permiten usar el estado y otras características de React sin la necesidad de escribir una clase.

Esta página responde algunas de las preguntas frecuentes acerca de los Hooks.

Estrategia de adopción

¿Qué versiones de React incluyen Hooks?

Empezando con React 16.8.0, se incluye una implementación estable de Hooks para:

  • React DOM
  • React DOM Server
  • React Test Renderer
  • React Shallow Renderer

Nótese que para habilitar los Hooks, todos los paquetes de React deben estar en la versión 16.8.0 o superior. Los Hooks no van a funcionar si olvidas, por ejemplo, actualizar React DOM.

React Native 0.59 y versiones superiores son compatibles con Hooks.

¿Necesito reescribir todos mis componentes que ya sean clases?

No. No hay planes de remover las clases de React — todos debemos seguir lanzando productos y no nos podemos dar el lujo de reescribir. Recomendamos usar Hooks en tu código nuevo.

¿Qué puedo hacer con Hooks que no pueda hacer con clases?

Los Hooks ofrecen una nueva, poderosa y expresiva forma de reusar funcionalidad entre componentes. La sección “Construyendo tus Propios Hooks” provee un vistazo a las posibilidades. Este artículo por uno de los miembros clave del equipo de React se adentra más en las nuevas capacidades que proveen los Hooks.

¿Qué tanto de mi conocimiento de React se mantiene relevante?

Los Hooks son una manera más directa de usar la características de React que ya conoces — como el estado, ciclo de vida, contexto, y las referencias (refs). No cambian de manera fundamental el funcionamiento de React, y tu conocimiento de componentes, props, y el flujo de datos de arriba hacia abajo sigue siendo igual de relevante.

Los Hooks tienen también su propia curva de aprendizaje. Si hay algo faltante en esta documentación, levanta un issue y trataremos de ayudar.

¿Debería usar Hooks, clases, o una mezcla de ambos?

Cuando estés listo, te recomendamos empezar a usar Hooks en los nuevos componentes que escribas. Asegúrate que todo tu equipo esté de acuerdo en usarlos, y que estén familiarizados con esta documentación. No recomendamos reescribir tus clases existentes a menos de que hayas planeado reescribirlas de cualquier manera (por ejemplo para arreglar bugs).

No puedes usar Hooks dentro de un Componente de Clase, pero definitivamente puedes mezclar componentes de clase y componentes de función con Hooks en un mismo árbol. Si un componente es una clase, o una función que utiliza Hooks es un detalle de implementación del Componente. A largo plazo, experamos que los Hooks sean la manera más usada de escribir Componentes de React.

¿Cubren los Hooks todos los casos de uso de las clases?

Nuestra meta es que los Hooks cubran todos los casos de uso de las clases lo más pronto posible. En este momento no existen equivalentes de los ciclos de vida poco comunes getSnapshotBeforeUpdate y componentDidCatch, pero planeamos añadirlos pronto.

Los Hooks aún son jóvenes, y algunas librerías de terceros podrían no ser compatibles con Hooks de momento.

¿Reemplazan los hooks a los render props y los Componentes de Orden Superior (HOC)?

En muchas ocasiones, render props y los componentes de orden superior, renderizan un sólo hijo. Pensamos que los Hooks son una forma más sencilla de soportar este caso de uso. Aún hay lugar para ambos patrones (por ejemplo, un scroller virtual podría tener un prop renderItem, o un componente que sea un contenedor visual podría tener su propia estructura de DOM). Pero en la mayoría de los casos, los Hooks serán suficiente y ayudaran a reducir la anidación en tu arbol.

Puedes seguir usando exactamente las mismas APIs que siempre has usado, seguirán funcionando.

En el futuro, nuevas versiones de estas librerías también podrían exportar Hooks personalizados como useRedux() or useRouter(), que te permitan usar las mismas características sin necesidad de usar componentes que los envuelvan.

¿Funcionan los Hooks con tipado estático?

Los Hooks fueron diseñados con el tipado estático en mente. Al ser funciones, son más fáciles de tipar que patrones como los componentes de orden superior (HOC). Las últimas definiciones para React de TypeScript y Flow incluyen soporte para Hooks.

Aún más importante, los Hooks personalizados tienen el poder de restringir la API de React si quisieras tiparlas de una manera más estricta. React te da las primitivas, pero puedes combinarlas de distintas maneras de las que proveemos por defecto.

¿Cómo probar Componentes que usan Hooks?

Desde el punto de vista de React, un componente que use Hooks, sigue siendo un componente normal. Si las herramientas de prueba que utilizas no dependen de los mecanismos internos de React, probar los componentes que usen Hooks, no debería ser diferente de probar cualquier otro componente.

Por ejemplo, asumamos que tenemos este componente de Conteo:

function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Vamos a probarlo usando React DOM. Para asegurarnos de que el comportamiento concuerda con lo que sucede en el browser, envolveremos el código, renderizándolo y actualizándolo usando llamadas a ReactTestUtils.act().

import React from 'react';
import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import Counter from './Counter';

let container;

beforeEach(() => {
  container = document.createElement('div');
  document.body.appendChild(container);
});

afterEach(() => {
  document.body.removeChild(container);
  container = null;
});

it('can render and update a counter', () => {
  // Probamos el primer render y efecto
  act(() => {
    ReactDOM.render(<Counter />, container);
  });
  const button = container.querySelector('button');
  const label = container.querySelector('p');
  expect(label.textContent).toBe('You clicked 0 times');
  expect(document.title).toBe('You clicked 0 times');

  // Probamos el segundo render y efecto
  act(() => {
    button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
  });
  expect(label.textContent).toBe('You clicked 1 times');
  expect(document.title).toBe('You clicked 1 times');
});

Las llamadas a act() también resolverán los efectos adentro de ellas.

Si necesitas probar un Hook personalizado, puedes hacerlo creando un componente en tu prueba, y usando tu Hook desde el mismo. Luego puedes probar el componente que escribiste.

Para reducir el boilerplate, recomendamos usar react-testing-library que está diseñada para promover pruebas que utilicen tus componentes como lo harían los usuarios finales.

¿Qué hacen cumplir las reglas de lint?

Proveemos un plugin de ESLint que hace cumplir las reglas de los Hooks para evitar bugs. Asume que cualquier función cuyo nombre empiece con ”use”, seguido de una letra mayúscula es un Hook. Reconocemos que esta heurística no es perfecta, y podría haber algunos falsos positivos, pero sin una convención que cubra a todo el ecosistema no hay manera de hacer que los Hooks funcionen bien en este aspecto — y nombres más largos desalientan a las personas de usar Hooks, o la convención.

En particular, la regla hace cumplir que:

  • Las llamadas a Hooks están dentro de una función cuyo nombre usa PascalCase (que se asume es un Componente), u otra función cuyo nombre empieza con ”use”, seguido de una letra mayúscula (por ejemplo useSomething, que se asume es un Hook personalizado).
  • Los Hooks se llaman en el mismo orden en cada llamado a render.

Hay algunas heurísticas más, y podrían cambiar con el tiempo mientras ajustamos las reglas para generar un balance entre encontrar bugs y encontrar falsos positivos.

De las clases a los Hooks

¿Cómo corresponden los métodos del ciclo de vida a los Hooks?

  • constructor: Los componentes de Función no requieren un constructor. Puedes inicializar el estado en la llamada a useState. Si el cálculo del estado inicial es costoso, puedes pasar una función a useState.

  • getDerivedStateFromProps: Agenda una actualización durante el renderizado.

  • shouldComponentUpdate: Ver React.memo abajo.

  • render: Es el cuerpo de el componente de función en sí.

  • componentDidMount, componentDidUpdate, componentWillUnmount: El Hook useEffect puede expresar todas las combinaciones de estos (incluyendo casos poco comunes).

  • componentDidCatch y getDerivedStateFromError: Aún no hay Hooks equivalentes a estos métodos, pero serán añadidos pronto.

¿Cómo puedo obtener datos con los Hooks?

Aquí hay un pequeño demo a modo introductorio. Para aprender más, consulta este artículo acerca de la obtención de datos con los Hooks.

¿Existe algo similar a las variables de instancia?

Si!, el Hook useRef() no es solo para referencias al DOM. El objeto “ref” es un contenedor genérico cuya propiedad current es mutable y puede contener cualquier valor, similar a una variable de instancia en una clase.

Puedes escribir en el desde adentro de useEffect:

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}

Si simplemente quisieramos setear un intérvalo no necesitaríamos le referencia (id podría ser local al efecto), pero es útil si queremos limpiar el intérvalo de un manejador de evento.

  // ...
  function handleCancelClick() {
    clearInterval(intervalRef.current);
  }
  // ...

Conceptualmente, puedes pensar en los refs como símiles a las variables de instancia en una clase. A menos que estés utilizando inicialización diferida (lazy initialization), evita setear referencias durante el renderizado — esto podría llevar a comportamiento inesperado. En cambio, generalmente querrás modificar las referencias en manejadores de eventos y efectos.

¿Debería usar una o muchas variables de estado?

Si vienes de las clases, podrías estar tentado a siempre llamar a useState() una sola vez y poner todo tu estado dentro de un solo objeto. Lo puedes hacer si quieres. Aquí hay un ejemplo que sigue el movimiento del mouse. mantenemos su posición y tamaño en el estado local:

function Box() {
  const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
  // ...
}

Ahora digamos que queremos escribir un poco de lógica que cambie left y top cuando el usuario mueva el mouse. Nota como mezclamos estos campos en el estado previo manualmente:

  // ...
  useEffect(() => {
    function handleWindowMouseMove(e) {
      // Spreading "...state" ensures we don't "lose" width and height
      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
    }
    // Note: this implementation is a bit simplified
    window.addEventListener('mousemove', handleWindowMouseMove);
    return () => window.removeEventListener('mousemove', handleWindowMouseMove);
  }, []);
  // ...

Esto se debe a que cuando actualizamos una variable de estado, reemplazamos su valor. Esto es diferente de this.setState en una clase, que mezcla los campos actualizados en el objeto.

Si extrañas esta mezcla automática, puedes escribir un Hook personalizado useLegacyState que mezcle las actualizaciones al objeto de estado. Sin embargo, Recomendamos dividir el estado en múltiples variables de estado, basado en los valores que tienden a cambiar juntos.

Por ejemplo, podríamos dividir el estado de nuestro componente en objetos position y size, y siempre reemplazar position sin la necesidad de mezclar.

function Box() {
  const [position, setPosition] = useState({ left: 0, top: 0 });
  const [size, setSize] = useState({ width: 100, height: 100 });

  useEffect(() => {
    function handleWindowMouseMove(e) {
      setPosition({ left: e.pageX, top: e.pageY });
    }
    // ...

Separar variables de estado independientes también tiene otro beneficio. Hace fácil extraer lógica relacionada en un Hook personalizado, por ejemplo:

function Box() {
  const position = useWindowPosition();
  const [size, setSize] = useState({ width: 100, height: 100 });
  // ...
}

function useWindowPosition() {
  const [position, setPosition] = useState({ left: 0, top: 0 });
  useEffect(() => {
    // ...
  }, []);
  return position;
}

Nota cómo podemos mover el llamado a useState para la variable de estado position y el efecto relacionado en un Hook personalizado sin cambiar su código. Si todo el estado estuviera en un solo objeto, extraerlo sería más difícil.

Ambas aproximaciones, poner todo el estado en un solo llamdo a useState, y usar un llamado a useState por cada campo, pueden funcionar. Los Componentes suelen ser más legibles cuando encuentras un balance entre ambos extremos, y agrupas partes del estado relacionadas en unas cuantas variables de estado independientes. Si la lógica del estado se vuelve muy compleja, recomendamos manejarla con un reductor, o un Hook personalizado.

¿Puedo correr un efecto solo cuando ocurran actualizaciones?

Este es un caso de uso poco común. Si lo necesitas, puedes usar una referencia mutable para guardar manualmente una bandera booleana que corresponde a si es el primer renderizado, o renderizados subsecuentes, luego puedes verificar la bandera en tu efecto. (Si te encuentras haciendo esto regularmente podrías crear un Hook Personalizado).

¿Cómo obtengo las props o el estado previo?

Actualmente lo puedes hacer manualmente con una referencia:

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

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}

Esto podría ser un poco complicado, pero puedes extraer la funcionalidad en un Hook personalizado:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

Nota como esto podría funcionar para props, estado, o cualquier otro valor calculado.

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

  const calculation = count * 100;
  const prevCalculation = usePrevious(calculation);
  // ...

Es posible que en el futuro React provea un usePrevious Hook por defecto, ya que es un caso de uso relativamente común.

Mira también el patrón recomendado para un estado derivado.

¿Por qué estoy viendo props o estado obsoletos dentro de mi función?

Cualquier función dentro de un componente, incluidos los manejadores de eventos y los efectos, “ven” los props y estado del renderizado en el que fueron creados. Por ejemplo, considera código como este:

function Example() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

Si hiciste clic primero en “Show alert” y luego incrementas el contador, la alerta mostrará la variable count en el momento en que hiciste click el botón “Show alert”. Esto previene errores causados por código que asume que los props y estado no cambian.

Si quieres intencionalmente leer el último estado de un callback asíncrono, podrías guardarla en una ref, mutarla y leer de ella.

Finalmente, otra razón posible para que veas props o estado obsoletos es que hayas usado la optimización del “array de dependencias” pero no especificaste correctamente todas las dependencias. Por ejemplo, si un efecto especifica [] como segundo argumento pero lee someProp dentro, continuará “viendo” el valor inicial de someProp. La solución pasa por o bien eliminar el array de dependencias, o arreglarlo. Aquí se explica como puedes lidiar con funciones, y aquí hay otras estrategias comunes para ejecutar efectos con menos frecuencia sin dejar de especifica despendencias incorrectamente.

Nota

Proporcionamos una regla de ESLint llamada exhaustive-deps como parte de nuestro paquete eslint-plugin-react-hooks. Esta regla advierte cuando las dependencias se especifican incorrectamente y sugiere una solución.

¿Cómo implemento getDerivedStateFromProps?

A pesar de que probablemente no lo necesites, en los pocos casos en los que sea necesario (por ejemplo implementando un componente <Transition>), puedes actualizar el estado en medio de la renderización. React correrá de nuevo el componente con el estado actualizado inmediatamente después de correr el primer renderizaod, así que no es costoso.

Aquí, guardamos el valor anterior del prop row en una variable de estado para poder comparar:

function ScrollView({row}) {
  let [isScrollingDown, setIsScrollingDown] = useState(false);
  let [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}

Esto puede parecer extraño en un principio, pero una actualización durante el renderizado es exactamente lo que siempre ha sido getDerivedStateFromProps conceptualmente.

¿Hay algo similar a forceUpdate?

Los Hooks useState y useReducer evitan las actualizaciones si el siguiente valor es igual al anterior. Mutar el estado y llamar a setState no causarán un re-renderizado.

Usualmente, no deberías mutar el estado local en React. Sin embargo, como una salida de emergencia, puedes usar un contador incremental para forzar un re-renderizado incluso si el estado no ha cambiado:

  const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

  function handleClick() {
    forceUpdate();
  }

Intenta evitar este patrón de ser posible.

¿Puedo crear una referencia (ref) a un Componente de función?

A pesar de que no deberías necesitar esto muy seguido, podrías exponer algunos métodos imperativos a un componente padre con con el Hook useImperativeHandle.

¿Cómo puedo medir un nodo del DOM?

Para medir la posición o el tamaño de un nodo del DOM, puedes usar una referencia mediante callback. React llamara el callback cuando la referencia sea agregada a un nodo diferente. Aquí hay un pequeño demo:

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

No escogimos useRef para este ejemplo porque un objeto de referencia no notifica sobre los cambios al valor actual de la referencia. Usando una referencia mediante callback lo aseguramos incluso si un componente hijo muestra el nodo medido después (por ejemplo, en respuesta a un click), aun somos notificados al respecto en el componente padre y podemos actualizar las medidas.

Recuerda que pasamos [] como un arreglo de dependencias a useCallback. Esto asegura que nuestro callback por referencia no cambie entre renderizados, y de esta manera React no lo llamara innecesariamente.

Si quieres, puedes extraer esta lógica a un Hook reusable:

function MeasureExample() {
  const [rect, ref] = useClientRect();
  return (
    <>
      <h1 ref={ref}>Hello, world</h1>
      {rect !== null &&
        <h2>The above header is {Math.round(rect.height)}px tall</h2>
      }
    </>
  );
}

function useClientRect() {
  const [rect, setRect] = useState(null);
  const ref = useCallback(node => {
    if (node !== null) {
      setRect(node.getBoundingClientRect());
    }
  }, []);
  return [rect, ref];
}

¿Qué significa [thing, setThing] = useState()?

Si no estás familiarizado con esta sintaxis, mira la explicación en la documentación de los Hooks de estado.

Optimizaciones de desempeño

¿Puedo saltarme un efecto durante las actualizaciones?

Si. Mira disparando un efecto condicionalmente. Ten en cuenta que no manejar las actualizaciones frecuentemente introduce bugs, por lo cual este no es el comportamiento por defecto.

¿Es seguro omitir funciones de la lista de dependencias?

De manera general, no.

function Example() {
  function doSomething() {
    console.log(someProp);
  }

  useEffect(() => {
    doSomething();
  }, []); // 🔴 Esto no es seguro (llama a `doSomething` que usa `someProp`)
}

Es difícil recordar cuáles props o estado son usadas por funciones fuera del efecto. Es por ello que usualmente querrás declarar las funciones que necesita el efecto dentro de él. De esta manera es fácil ver los valores del ámbito del componente de los que depende ese efecto:

function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);
    }

    doSomething();
  }, [someProp]); // ✅ Bien (nuestro efecto solo usa `someProp`)
}

Si luego de ello aún no usas ningún valor del ámbito del componente, es seguro especificar []:

useEffect(() => {
  function doSomething() {
    console.log('hello');
  }

  doSomething();
}, []); // ✅ Bien en este ejemplo, porque no usamos *ninguno* de los valores del ámbito del componente

En dependencia de tu caso de uso, hay otras opciones descritas debajo:

Nota

Proporcionamos una regla de ESLint llamada exhaustive-deps como parte de nuestro paquete eslint-plugin-react-hooks. Esta regla te ayuda a encontrar componentes que no manejan las actualizaciones consistentemente.

Veamos por qué esto importa.

Si especificas un lista de dependencias como el último argumento de useEffect, useMemo, useCallback, o useImperativeHandle, debe incluir todos los valores usados dentro que participan en el flujo de datos de React. Aquí se incluyen props, estado y todo lo que esté derivado de ellos.

Únicamente es seguro omitir una función de la lista de dependencias si nada dentro (o las funciones a las que se llama) referencia props, estado, o valores de ellos. Este ejemplo tiene un error:

function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);

  async function fetchProduct() {
    const response = await fetch('http://myapi/product' + productId); // Usa la prop productId
    const json = await response.json();
    setProduct(json);
  }

  useEffect(() => {
    fetchProduct();
  }, []); // 🔴 No válido, porque `fetchProduct` usa `productId`
  // ...
}

La solución recomendada es mover la función dentro de tu efecto. Ello facilta ver qué props o estado usa tu efecto, y asegura que todos son declarados:

function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);

  useEffect(() => {
    // Al mover esta función dentro del efecto, podemos ver claramente los valores que usa.
    async function fetchProduct() {
      const response = await fetch('http://myapi/product' + productId);
      const json = await response.json();
      setProduct(json);
    }

    fetchProduct();
  }, [productId]); // ✅ Válido, porque nuestro efecto solo usa productId
  // ...
}

Esto también te permite manejar respuestas fuera de orden con una variable local dentro del efecto:

  useEffect(() => {
    let ignore = false;
    async function fetchProduct() {
      const response = await fetch('http://myapi/product/' + productId);
      const json = await response.json();
      if (!ignore) setProduct(json);
    }
    
    fetchProduct();
    return () => { ignore = true };
  }, [productId]);

Movimos la función dentro del efecto, de manera tal que no necesite estar en su lista de dependencias.

Consejo

Consulta este pequeño demo y este artículo para aprender más sobre la obtención de datos con Hooks.

Si por alguna razón no puedes mover una funció dentro de un efecto, hay otras opciones:

  • Puedes intentar mover esa función fuera de tu componente. En ese caso, se garantiza que la función no referencie ningúna prop o estado, y además no necesita estar en la lista de dependencias.
  • Si la función que estás llamando es un cálculo puro y es seguro llamarla mientras se renderiza, puedes llamarla fuera del efecto, y hacer que el efecto dependa del valor devuelto.
  • Cómo último recurso, puedes añadir una función a las dependencias del efecto, pero envolver su definición en el Hook useCallback. Esto asegura que no cambie en cada renderizado a menos que sus propias dependencias también cambien:
function ProductPage({ productId }) {
  // ✅ Envolver con useCallback para evitar que cambie en cada renderizado
  const fetchProduct = useCallback(() => {
    // ... Hace algo con productId ...
  }, [productId]); // ✅ All useCallback dependencies are specified

  return <ProductDetails fetchProduct={fetchProduct} />;
}

function ProductDetails({ fetchProduct })
  useEffect(() => {
    fetchProduct();
  }, [fetchProduct]); // ✅ Se especifican todas las dependencias de useEffect
  // ...
}

Nota que en el ejemplo de arriba necesitamos mantener la función en la lista de dependencias. Esto asegura que un cambio en la prop productId de ProductPage automáticamente desencadena una nueva obtención de datos en el componente ProductDetails.

¿Qué puedo hacer si las dependencias de un efecto cambian con mucha frecuencia?

A veces, tu efecto puede estar usando un estado que cambia con demasiada frecuencia. Puedes estar tentado a omitir ese estado de una lista de dependencias, pero eso usualmente conduce a errores:

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1); // Este efecto depende del estado `count`
    }, 1000);
    return () => clearInterval(id);
  }, []); // 🔴 Error: `count` no se especifica como una dependencia

  return <h1>{count}</h1>;
}

El conjunto vacío de dependencias, [], significa que el efecto solo se ejecutará cuando el componente se monte, y no en cada rerenderizado. El problema es que dentro del callback de setInterval, el valor de count no cambia, porque hemos creado una clausura con el valor de count en 0 como estaba cuando la función callback del efecto se ejecutó. Cada segundo, esta función llama a setCount(0 + 1), por lo que el contador count nunca sube de 1.

Especificar [count] como una lista de dependencias solucionaría el error, pero causaría que el intervalo se reiniciara con cada cambio. Efectivamente, cada setInterval tendría una oportunidad para ejecutarse antes de limpiarse (de forma similar a setTimeout). Esto puede no ser deseable. Para solucionarlo, podemos usar la forma de actualización funcional de setState. Nos permite especificar cómo el estado necesita cambiar sin referenciar el estado actual:

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ Esto no depende en la variable `count` de afuera
    }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ Nuestro efecto no usa ninguna variable en el ámbito del componente

  return <h1>{count}</h1>;
}

(La identidad de la función setCount se garantiza que sea estable, por lo que es seguro omitirla.)

Ahora, el callback de setInterval se ejecuta una vez cada segundo, pero cada vez la llamada interna a setCount puede utilizar un valor actualizado para count (llamado c en este callback).

En casos más complejos (como en el que un estado depende de otro estado), intenta mover la lógica de actualización del estado fuera del efecto con el Hook useReducer. Este artículo ofrece un ejemplo de cómo puedes hacerlo. La identidad de la función dispatch de useReducer es siempre estable, incluso si la función reductora se declara dentro del componente y lee sus props.

Como último recurso, si quieres algo como this en una clase, puedes usar una ref para tener una variable mutable. Luego puedes escribirla y leerla. Por ejemplo:

function Example(props) {
  // Mantener las últimas props en una ref.
  let latestProps = useRef(props);
  useEffect(() => {
    latestProps.current = props;
  });

  useEffect(() => {
    function tick() {
      // Leer la últimas props en cualquier momento
      console.log(latestProps.current);
    }

    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []); // Este fecto nunca se vuelve a ejectuar
}

Únicamente haz esto si no puedes encontrar una mejor alternativa, dado que depender en mutaciones hace que los componenentes sean menos predecibles. Si hay un patrón específico que no se traduce bien, abre una incidencia con un ejemplo de código ejecutable e intentaremos ayudar.

¿Cómo implemento shouldComponentUpdate?

Puedes envolver un componente de función con React.memo, para comparar sus props superficialmente.

const Button = React.memo((props) => {
  // Tu Componente
});

No es un Hook porque no se compone como lo hacen los Hooks. React.memo es equivalente a PureComponent, pero solo compara las props. (Puedes añadir un segundo argumento para especificar una función de comparación personalizada, que reciba las props viejas y las nuevas. Si retorna true, se obvia la actualización).

React.memo no compara el estado porque no existe un único objeto de estado para comparar. Pero puedes hacer los hijos puros también, o incluso optimizar hijos individualmente con useMemo.

¿Cómo memorizar (memoize) los cálculos?

El Hook useMemo te deja cachear cálculos entre múltiples renders “recordando” el cálculo anterior.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Este código llama a computeExpensiveValue(a, b). Pero si las dependencias [a, b] no han cambiado useMemo evita llamarle de nuevo y simplemente reusa el último valor que había retornado.

Recuerda que la función que se pasa a useMemo corre durante el renderizado. No hagas nada allí que no harías durante el renderizado. Por ejemplo, los efectos secundarios deberían estar en useEffect, no en useMemo.

Puedes depender de useMemo como una mejora de desempeño, pero no como una garantía semántica. En el futuro, React podría escoger “olvidar” algunos valores previamente memorizados y recalcularlos en el siguiente renderizado, por ejemplo para liberar memoria para los components que no se ven en pantalla. Escribe to código de manera que pueda funcionar sin useMemo — y luego añádelo para mejorar el desempeño. (Para casos extraños en los que un valor nunca deba ser recalculado, puedes inicializar una ref. de manera diferida).

Convenientemente useMemo también te deja saltar re-renderizados costosos de un hijo:

function Parent({ a, b }) {
  // Solo re-renderizado si `a` cambia:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Solo re-renderizado si `b` cambia:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

Ten en cuenta que este método no funcionará en un ciclo porque las llamadas a Hooks no pueden ser puestas dentro de ciclos. Pero puedes extraer un componente separado para el item de la lista, y llamar useMemo allí.

¿Cómo crear objetos costosos de manera diferida (lazy)?

useMemo te permite memorizar un cálculo costoso si las dependencias son las mismas, sin embargo, solo funciona como un indicio, y no garantiza que el cálculo no se correrá de nuevo. Pero a veces necesitas estar seguro que un objeto sólo se cree una vez.

El primer caso de uso común es cuando crear el estado inicial es costoso:

function Table(props) {
  // ⚠️ createRows() se llama en cada renderizado
  const [rows, setRows] = useState(createRows(props.count));
  // ...
}

Para evadir re-crear el estado inicial ignorado, podemos pasar una función a useState:

function Table(props) {
  // ✅ createRows() solo se llama una vez.
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}

React solo llama a esta función durante el primer renderizado. Mira el manual de referencia de la API de useState.

También podrías querer ocasionalmente evitar re-crear el valor inicial de useRef. Por ejemplo, tal vez quieres asegurarte que que alguna instancia de una clase imperativa solo se cree una vez:

function Image(props) {
  // ⚠️ IntersectionObserver se crea en cada renderizado
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}

useRef no acepta una sobrecarga especial con una función como useState. En cambio, puedes crear tu propia función que cree e inicialize el valor de manera diferida:

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver se crea de manera diferida una vez.
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // Cuando lo necesites, llama a getObserver()
  // ...
}

Esto ayuda a evitar crear un objeto costoso hasta que sea realmente necesario pro primera vez. Si usas Flow o TypeScript, puedes darle a getOberver un typo no nulo por conveniencia.

¿Son los hooks lentos debido a la creación de funciones en el render?

No. en los navegadores modernos, el desempeño en crudo de los closures comparado con el de las clases no difiere de manera significativa, exceptuando casos extremos.

Adicionalmente, considera que el diseño de los Hooks es más eficiente en un par de sentidos:

  • Evitan gran parte de la complejidad (trabajo extra) que las clases requieren, como el costo de crear instancias de clase y ligar (bind) los manejadores de eventos en el constructor.

  • El código idiómatico usando Hooks no requiere el anidado profundo de componentes que es prevalente en bases de código que utilizan componentes de orden superior, render props, y contexto. Con árboles de componentes más pequeños, React tiene menos trabajo que realizar.

Tradicionalmente, las preocupaciones de desempeño alrededor de funciones inline en React han estado relacionadas con como al pasar nuevos callbacks en cada renderizado rompe optimizaciones con shouldComponentUpdate en los componentes hijos. Los Hooks pueden resolver este problema desde tres ángulos diferentes.

  • El Hook useCallback te permite mantener la misma referencia al callback entre re-renderizados, de manera que shouldComponentUpdate no se rompe.

    // No cambia a menos que `a` o `b` cambien
    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]);
  • El Hook useMemo hace más fácil controlar cuando se deberían actualizar hijos individualmente, reduciendo la necesidad de componentes puros.

  • Finalmente el Hook useReducer reduce la necesidad de pasar callbacks profundamente, como se explica en la siguiente sección.

¿Cómo evitar pasar callbacks hacia abajo?

Nos hemos dado cuenta que la mayoría de personas no disfrutan pasar callbacks manualmente a través de cada nivel del árbol de componentes. A pesar de ser más explícito, se puede sentir como mucha “plomería”.

En árboles de componentes muy grandes, una alternativa que recomendamos es pasar una función dispatch desde useReducer a través del contexto (Context):

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // Nota: `dispatch` no cambia entre re-renderizados
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

Todo hijo en el árbol dentro de TodosApp puede usar la función dispatch para pasar acciones hacia arriba, a TodosApp:

function DeepChild(props) {
  // Si queremos realizar una acción, podemos obtener dispatch del contexto.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

Esto es más conveniente desde la perspectiva de mantenimiento (no hay necesidad de seguir re-enviando callbacks) y resuelve el problema de los callbacks por completo. Pasar dispatch de esta manera es el patrón recomendado para actualizaciones profundas.

Ten en cuenta que aún puedes decidir si quieres pasar el estado de la aplicación hacia abajo como props (más explícito) o como contexto (más conveniente para actualizaciones profundas). Si usas el contexto para pasar el estado haci abajo también, usa dos tipos diferentes de contexto — el contexto de dispatch nunca cambia, así que los componentes que lean de el no necesitan re-renderizarse a menos que también necesiten el estado de la aplicación.

¿Cómo leer un valor que cambia frecuentemente desde useCallback?

Nota

Recomendamos pasar dispatch a través del contexto en vez de callbacks individuales en las props. El siguiente método sólo se menciona para efectos de completitud y como una salida de emergencia.

También ten en cuenta que este patrón puede causar problemas en el modo concurrente. Planeamos proveer alternativas más ergonómicas en el futuro, pero la solución más segura en este momento es siempre invalidar el callback si alguno de los valores de los que depende cambia.

En algunos extraños casos puede que necesites memorizar un callback con useCallback, pero la memorización no funciona muy bien, debido a que la función interna debe ser re-creada muy seguido. Si la función que estás memorizando es un manejador de eventos y no se usa durante el renderizado, puedes utilizar ref como una variable de estado, I guardar el último valor manualmente:

function Form() {
  const [text, updateText] = useState('');
  const textRef = useRef();

  useEffect(() => {
    textRef.current = text; // Se escribe en la referencia
  });

  const handleSubmit = useCallback(() => {
    const currentText = textRef.current; // See lee desde la ref
    alert(currentText);
  }, [textRef]); // No se recrea handleSubmit como [text] lo haría

  return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}

Este es un patrón relativamente complicado, pero muestra que puedes utilizar esta salida de emergencia como optimización de ser necesario. Es más fácil de llevar si lo extraes a un Hook personalizado:

function Form() {
  const [text, updateText] = useState('');
  // Será memorizado incluso si `text` cambia:
  const handleSubmit = useEventCallback(() => {
    alert(text);
  }, [text]);

  return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}

function useEventCallback(fn, dependencies) {
  const ref = useRef(() => {
    throw new Error('Cannot call an event handler while rendering.');
  });

  useEffect(() => {
    ref.current = fn;
  }, [fn, ...dependencies]);

  return useCallback(() => {
    const fn = ref.current;
    return fn();
  }, [ref]);
}

En cualquier caso, no recomendamos este patrón y solo lo mostramos aquí para efectos de completitud. En cambio, es preferible evitar pasar callbacks profundamente.

Bajo el capó

¿Cómo asocia React las llamadas a los Hooks con Componentes?

React está pendiente del componente que actualmente se está renderizando. Gracias a las Reglas de los Hooks, sabemos que los Hooks sólo son llamados desde componente de React (o Hooks personalizados — los cuales también sólo son llamados desde componentes de React).

Hay una lista interna de “celdas de memoria” asociadas con cada componente. Son simplemente objetos de JavaScript donde podemos poner algunos datos. Cuando llamas un Hook como useState(), este lee la celda actual (o la inicializa durante el primer llamado), y luego mueve el puntero a la siguiente. Así es como llamados múltiples a useState() obtienen estados locales independientes.

¿Cuáles son los antecedentes de los Hooks?

Los Hook sintetizan ideas de muchas fuentes diferentes:

Sebastian Markbåge propuso el diseño original de los Hooks, luego refinado por Andrew Clark, Sophie Alpert, Dominic Gannaway, y otros miembros del equipo de React.