Puertas de escape

Avanzado

Algunos de tus componentes puede que necesiten controlar y sincronizarse con sistemas externos a react. Por ejemplo, puede que necesites enfocar un input usando la API del navegador, reproducir o pausar un reproductor de video implementado sin React, o conectar y escuchar mensajes de un servidor remoto. En este capitulo, aprenderás las puertas de escape que te permiten “salir” de React y conectarte con sistemas externos. La mayoría de la lógica de tu aplicación y flujo de datos no deberían depender de estas características.

Referencia valores con refs

Cuando quieres que un componente «recuerde» cierta información, pero no quieres que esa información desencadene nuevos renderizados, puedes usar una ref:

const ref = useRef(0);

Al igual que un estado, las refs son retenidas por React entre nuevos renderizados. Sin embargo, al asignar un nuevo valor al estado se vuelve a renderizar el componente. ¡Cambiar el valor de la ref no lo hace! Puedes acceder al valor actual de esa ref a través de la propiedad ref.current.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('Has pulsado ' + ref.current + ' veces!');
  }

  return (
    <button onClick={handleClick}>
      ¡Hazme clic!
    </button>
  );
}

Una ref es como un bolsillo secreto de tu componente que React no puede rastrear. Por ejemplo, puedes usar refs para almacenar timeout IDs, DOM elements, y otros objectos que no tienen un impacto en el resultado del renderizado de tu componente.

¿Listo para aprender este tema?

Lee Referenciar valores con Refs para aprender como usar las refs y recordar información.

Lee más

Manipular el DOM con refs

React automáticamente actualiza el DOM para coincidir con el resultado de tu renderizado, por lo que tus componentes no se necesitarán manipular frecuentemente. Sin embargo, algunas veces puede que necesites acceder a los elementos del DOM gestionados por React—por ejemplo, referenciar un nodo, desplazarse hacia el, o medir su tamaño y posición. No hay una manera integrada de hacer esto en React, así que necesitarás una ref hacia el nodo del DOM. Por ejemplo, al hacer click en el botón se enfocará el input usando una ref:

import { useRef } from 'react';

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

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Enfoca el input
      </button>
    </>
  );
}

¿Listo para aprender este tema?

Lee Manipular el DOM con Refs para aprender como acceder a los elementos del DOM manejados por React.

Lee más

Sincronizar con efectos

Algunos componentes necesitan sincronizarse con sistemas externos. Por ejemplo, es posible que desees controlar un componente que no sea de React basado en el estado de React, establecer una conexión a un servidor, enviar reporte de analíticas cuando un componente aparece en la pantalla. A diferencia de los manejadores de eventos, que permiten manejar eventos concretos, los efectos te permiten ejecutar algún código después de renderizar. Úsalos para sincronizar tu componente con un sistema externo a React.

Presiona Reproducir/Pausar unas veces y mira como el reproductor de video permanece sincronizado al valor de la prop isPlaying:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pausar' : 'Reproducir'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

Algunos efectos pueden «limpiarse» por sí mismos. Por ejemplo, un efecto que establece una conexión a un servidor de chat debería retornar una función de limpieza que le dice a React como desconectar su componente de ese servidor:

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

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Bienvenido al chat!</h1>;
}

En desarrollo, React ejecutará inmediatamente y limpiará tu efecto una vez más. Es por esto que ves »✅ Conectando…»` impreso dos veces. Esto asegura que no olvides de aplicar la función de limpieza.

¿Listo para aprender este tema?

Lee Sincronizar con Efectos para aprender a como sincronizar componentes con sistemas externos.

Lee más

Puede que no necesites un efecto

Los efectos son una puerta de escape del paradigma de React. Te permiten «salir» de React y sincronizar tus componentes con algún sistema externo. Si no hay un sistema externo involucrado (por ejemplo, si quieres actualizar el estado de un componente cuando cambien algunas props o estados), no deberías necesitar un efecto. La eliminación de efectos innecesarios harán tu código más fácil de comprender, más rápido de ejecutar y menos propenso a errores.

Hay dos casos comunes en los que no necesitas efectos:

  • No necesitas efectos para transformar los datos para el renderizado.
  • No necesitas efectos para manejar eventos de usuario.

Por ejemplo, no necesitas un efecto para ajustar algún estado basado en otro estado:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');

// 🔴 Evita: estado redundante y efecto innecesario
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}

En lugar a eso, calcula todo lo que puedas mientras renderizas:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Bien: calculado durante el renderizado
const fullName = firstName + ' ' + lastName;
// ...
}

Sin embargo, necesitas efectos para sincronizar con sistemas externos.

¿Listo para aprender este tema?

Lee Puede que no necesites de un efecto para aprender como eliminar efectos innecesarios.

Lee más

Ciclo de vida de los efectos reactivos

Los efectos tienen un ciclo de vida diferente al de los componentes. Los componentes se pueden montar, actualizar, o desmontar. Un efecto puede únicamente hacer dos cosas: empezar a sincronizar algo, y a detener ese sincronizado más adelante. Este ciclo puede suceder varias veces si tu efecto depende de props y estados que pueden cambiar sobre el tiempo.

Este efecto depende del valor de la prop roomId. Las props son valores reactivos, que significan que pueden cambiar al volver a renderizar. Observa que el efecto se vuelve a sincronizar (y se vuelve a conectar al servidor) si roomId cambia:

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

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Bienvenido a la sala {roomId}!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escoge la sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

React proporciona una regla al linter para comprobar que hayas especificado correctamente las dependencias de tus efectos. Si olvidas de especificar roomId en la lista de dependencias en el ejemplo anterior, el linter encontrará ese error automáticamente.

¿Listo para aprender este tema?

Lee Ciclo de vida de eventos reactivos para aprender como el ciclo de vida de un efecto es diferente al de un componente.

Lee más

Separar los eventos de los efectos

En construcción

Esta sección describe una API experimental que aún no se ha publicado en una version estable de React.

Los eventos manejadores únicamente se vuelven a ejecutar cuando realizas de nuevo la misma interacción. A diferencia de los eventos manejadores, los efectos se vuelven a sincronizar si alguno de los valores que leen, como props o estados, son diferentes a los del ultimo renderizado. Algunas veces, quieres una mezcla de ambos comportamientos: un efecto que se vuelve a ejecutar en respuesta de algunos valores.

Todo el código dentro de los efectos es reactivo. Se ejecutará de nuevo si algún valor reactivo que lee ha cambiado debido a una nueva renderización. Por ejemplo, este efecto volverá a conectarse con el chat si roomId o theme han cambiado:

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Bienvenido a la sala {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Escoge la sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Usa el tema oscuro
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'} 
      />
    </>
  );
}

Esto no es lo ideal. Quieres volver a conectar con el chat únicamente si el roomId ha cambiado. Cambiar el tema no debería volver a conectarse con el chat! Mueve el código que lee el tema fuera de tu efecto hacia un evento de efecto:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Conectado!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Bienvenido a la sala {roomId}!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Escoge la sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Usa el tema oscuro
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'} 
      />
    </>
  );
}

El código dentro de los eventos de efecto no son reactivos, así que cambiando el theme no hace que tu efecto se vuelva a conectar más.

¿Listo para aprender este tema?

Lee Separar eventos de efectos para aprender como evitar que algunos valores vuelvan a desencadenar efectos.

Lee más

Eliminar dependencias de efectos

Cuando escribes un efecto, el linter verificará que hayas incluido cada valor reactivo (como props y estados) que el efecto lee en la lista de tus dependencias de efectos. Esto asegura que tus efectos permanezcan sincronizados con las últimas props y estados de tu componente. Las dependencias innecesarias pueden causar que tu efecto se ejecute frecuentemente, incluso llegar a crear un ciclo infinito. La manera en que los elimines dependerá de cada caso.

Por ejemplo, este efecto depende del objecto options el cual se recrea cada vez que cambies el input:

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

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]);

  return (
    <>
      <h1>Bienvenido a la sala {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escoge la sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

No quieres que el chat se vuelva a conectar cada vez que empieces a escribir un mensaje. Para resolver este problema, mueve el objecto options dentro del efecto así solo depende únicamente del string roomId:

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

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Bienvenido a la sala {roomId}!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Escoge la sala de chat:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Fíjate que no empezaste editando la lista de dependencia para eliminar la dependencia options. Eso sería un error. En lugar de eso, cambiaste el código alrededor así que la dependencia se volvió innecesaria. Piensa en la lista de dependencias como una lista de todos los valores reactivos utilizados por el código de tu efecto. Tú no escogiste intencionadamente que poner en esa lista. La lista describe tu código. Para cambiar la lista de dependencias, cambia el código.

¿Listo para aprender este tema?

Lee Eliminar dependencias de efectos para aprender a hacer que tu efecto se repita con menos frecuencia.

Lee más

Reutilizar la lógica con hooks personalizados

React viene con hooks incorporados como useState, useContext, y useEffect. Algunas veces, desearás que existiera un hook para un propósito especifico: por ejemplo, para llamar datos, para saber si el usuario esta conectado, o para conectarse a una sala de chat. Para realizar esto, puedes crear tus propios hooks de acuerdo a las necesidades de tu aplicación.

En este ejemplo, el hook personalizado usePointerPosition rastrea la posición del cursor, mientras que el hook personalizado useDelayedValue retorna un valor que esta «rezagado» con respecto al valor que le pasaste por un cierto número de milisegundos. Mueve el cursor sobre el área de vista previa del sandbox para ver un rastro de puntos en movimiento que siguen al cursor:

import { usePointerPosition } from './usePointerPosition.js';
import { useDelayedValue } from './useDelayedValue.js';

export default function Canvas() {
  const pos1 = usePointerPosition();
  const pos2 = useDelayedValue(pos1, 100);
  const pos3 = useDelayedValue(pos2, 200);
  const pos4 = useDelayedValue(pos3, 100);
  const pos5 = useDelayedValue(pos4, 50);
  return (
    <>
      <Dot position={pos1} opacity={1} />
      <Dot position={pos2} opacity={0.8} />
      <Dot position={pos3} opacity={0.6} />
      <Dot position={pos4} opacity={0.4} />
      <Dot position={pos5} opacity={0.2} />
    </>
  );
}

function Dot({ position, opacity }) {
  return (
    <div style={{
      position: 'absolute',
      backgroundColor: 'pink',
      borderRadius: '50%',
      opacity,
      transform: `translate(${position.x}px, ${position.y}px)`,
      pointerEvents: 'none',
      left: -20,
      top: -20,
      width: 40,
      height: 40,
    }} />
  );
}

Puedes crear hooks personalizados, componerlos juntos, pasar datos entre ellos y reutilizarlos entre componentes. A medida que tu aplicación crezca, escribirás menos efectos a mano porque podrás reutilizar los hooks personalizados que ya hayas escrito. También existen excelentes hooks personalizados mantenidos por la comunidad de React.

¿Listo para aprender este tema?

Lee Reutilizar la lógica con hooks personalizados para aprender como compartir lógica entre componentes.

Lee más

Qué sigue?

Dirígete a Referenciar valores con refs para empezar a leer este capítulo!