> ## Documentation Index
> Fetch the complete documentation index at: https://adminroletesting-justin-client-exports.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# React

> Crea elementos interactivos y reutilizables con componentes de React personalizados en tu documentación de Mintlify usando JSX y estado.

export const ColorGenerator = () => {
  const [hue, setHue] = useState(165);
  const [saturation, setSaturation] = useState(84);
  const [lightness, setLightness] = useState(31);
  const [colors, setColors] = useState([]);
  useEffect(() => {
    const newColors = [];
    for (let i = 0; i < 5; i++) {
      const l = Math.max(10, Math.min(90, lightness - 20 + i * 10));
      newColors.push(`hsl(${hue}, ${saturation}%, ${l}%)`);
    }
    setColors(newColors);
  }, [hue, saturation, lightness]);
  const copyToClipboard = color => {
    navigator.clipboard.writeText(color).then(() => {
      console.log(`Copied ${color} to clipboard!`);
    }).catch(err => {
      console.error("Failed to copy: ", err);
    });
  };
  return <div className="p-4 border dark:border-white/10 rounded-2xl not-prose">
      <div className="space-y-4">
        <div className="space-y-2">
          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Hue: {hue}°
            <input type="range" min="0" max="360" value={hue} onChange={e => setHue(Number.parseInt(e.target.value))} className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1" style={{
    background: `linear-gradient(to right, 
                  hsl(0, ${saturation}%, ${lightness}%), 
                  hsl(60, ${saturation}%, ${lightness}%), 
                  hsl(120, ${saturation}%, ${lightness}%), 
                  hsl(180, ${saturation}%, ${lightness}%), 
                  hsl(240, ${saturation}%, ${lightness}%), 
                  hsl(300, ${saturation}%, ${lightness}%), 
                  hsl(360, ${saturation}%, ${lightness}%))`
  }} />
          </label>

          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Saturation: {saturation}%
            <input type="range" min="0" max="100" value={saturation} onChange={e => setSaturation(Number.parseInt(e.target.value))} className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1" style={{
    background: `linear-gradient(to right, 
                  hsl(${hue}, 0%, ${lightness}%), 
                  hsl(${hue}, 50%, ${lightness}%), 
                  hsl(${hue}, 100%, ${lightness}%))`
  }} />
          </label>

          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Lightness: {lightness}%
            <input type="range" min="0" max="100" value={lightness} onChange={e => setLightness(Number.parseInt(e.target.value))} className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1" style={{
    background: `linear-gradient(to right, 
                  hsl(${hue}, ${saturation}%, 0%), 
                  hsl(${hue}, ${saturation}%, 50%), 
                  hsl(${hue}, ${saturation}%, 100%))`
  }} />
          </label>
        </div>

        <div className="flex space-x-2">
          {colors.map((color, idx) => <div key={idx} className="h-16 rounded flex-1 cursor-pointer transition-transform hover:scale-105" style={{
    backgroundColor: color
  }} title={`Click to copy: ${color}`} onClick={() => copyToClipboard(color)} />)}
        </div>

        <div className="text-sm font-mono text-zinc-950/70 dark:text-white/70">
          <p>
            Base color: hsl({hue}, {saturation}%, {lightness}%)
          </p>
        </div>
      </div>
    </div>;
};

Crea elementos interactivos en tus documentos usando [componentes de React](https://react.dev) y [hooks](https://react.dev/reference/react/hooks) directamente en archivos MDX.

<div id="inline-components">
  ## Componentes en línea
</div>

Declara componentes directamente en tu archivo MDX:

export const Counter = () => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  return <div className="flex items-center justify-center">
      <div className="flex items-center rounded-xl overflow-hidden border border-zinc-950/20 dark:border-white/20">
        <button onClick={decrement} className="flex items-center justify-center h-8 w-8 text-zinc-950/80 dark:text-white/80 border-r border-zinc-950/20 dark:border-white/20" aria-label="Decrease">
          -
        </button>

        <div className="flex text-sm items-center justify-center h-8 px-6 text-zinc-950/80 dark:text-white/80 font-medium min-w-[4rem] text-center">
          {count}
        </div>

        <button onClick={increment} className="flex items-center justify-center h-8 w-8 text-zinc-950/80 dark:text-white/80 border-l border-zinc-950/20 dark:border-white/20" aria-label="Increase">
          +
        </button>
      </div>
    </div>;
};

<Counter />

```mdx theme={null}
export const Counter = () => {
  const [count, setCount] = useState(0)
  const increment = () => setCount(count + 1)
  const decrement = () => setCount(count - 1)

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  )
}

<Counter />
```

<div id="import-components">
  ## Importar componentes
</div>

Los archivos de componentes deben estar en la carpeta `/snippets/`. Más información sobre [fragmentos reutilizables](/es/create/reusable-snippets).

<Note>
  Los imports anidados no son compatibles. Importa todos los componentes referenciados directamente en el archivo MDX padre.
</Note>

Crea un archivo de componente en `snippets/`:

```mdx /snippets/color-generator.jsx [expandable] theme={null}
export const ColorGenerator = () => {
  const [hue, setHue] = useState(180)
  const [saturation, setSaturation] = useState(50)
  const [lightness, setLightness] = useState(50)
  const [colors, setColors] = useState([])

  useEffect(() => {
    const newColors = []
    for (let i = 0; i < 5; i++) {
      const l = Math.max(10, Math.min(90, lightness - 20 + i * 10))
      newColors.push(`hsl(${hue}, ${saturation}%, ${l}%)`)
    }
    setColors(newColors)
  }, [hue, saturation, lightness])

  const copyToClipboard = (color) => {
    navigator.clipboard
      .writeText(color)
      .then(() => {
        console.log(`Copied ${color} to clipboard!`)
      })
      .catch((err) => {
        console.error("Failed to copy: ", err)
      })
  }

  return (
    <div className="p-4 border dark:border-zinc-950/80 rounded-xl not-prose">
      <div className="space-y-4">
        <div className="space-y-2">
          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Hue: {hue}°
            <input
              type="range"
              min="0"
              max="360"
              value={hue}
              onChange={(e) => setHue(Number.parseInt(e.target.value))}
              className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1"
              style={{
                background: `linear-gradient(to right, 
                  hsl(0, ${saturation}%, ${lightness}%), 
                  hsl(60, ${saturation}%, ${lightness}%), 
                  hsl(120, ${saturation}%, ${lightness}%), 
                  hsl(180, ${saturation}%, ${lightness}%), 
                  hsl(240, ${saturation}%, ${lightness}%), 
                  hsl(300, ${saturation}%, ${lightness}%), 
                  hsl(360, ${saturation}%, ${lightness}%))`,
              }}
            />
          </label>

          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Saturation: {saturation}%
            <input
              type="range"
              min="0"
              max="100"
              value={saturation}
              onChange={(e) => setSaturation(Number.parseInt(e.target.value))}
              className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1"
              style={{
                background: `linear-gradient(to right, 
                  hsl(${hue}, 0%, ${lightness}%), 
                  hsl(${hue}, 50%, ${lightness}%), 
                  hsl(${hue}, 100%, ${lightness}%))`,
              }}
            />
          </label>

          <label className="block text-sm text-zinc-950/70 dark:text-white/70">
            Lightness: {lightness}%
            <input
              type="range"
              min="0"
              max="100"
              value={lightness}
              onChange={(e) => setLightness(Number.parseInt(e.target.value))}
              className="w-full h-2 bg-zinc-950/20 rounded-lg appearance-none cursor-pointer dark:bg-white/20 mt-1"
              style={{
                background: `linear-gradient(to right, 
                  hsl(${hue}, ${saturation}%, 0%), 
                  hsl(${hue}, ${saturation}%, 50%), 
                  hsl(${hue}, ${saturation}%, 100%))`,
              }}
            />
          </label>
        </div>

        <div className="flex space-x-1">
          {colors.map((color, idx) => (
            <div
              key={idx}
              className="h-16 rounded flex-1 cursor-pointer transition-transform hover:scale-105"
              style={{ backgroundColor: color }}
              title={`Click to copy: ${color}`}
              onClick={() => copyToClipboard(color)}
            />
          ))}
        </div>

        <div className="text-sm font-mono text-zinc-950/70 dark:text-white/70">
          <p>
            Base color: hsl({hue}, {saturation}%, {lightness}%)
          </p>
        </div>
      </div>
    </div>
  )
}
```

Luego impórtalo y úsalo:

```mdx theme={null}
import { ColorGenerator } from "/snippets/color-generator.jsx"

<ColorGenerator />
```

<ColorGenerator />

<div id="considerations">
  ## Consideraciones
</div>

* **SEO**: Es posible que los motores de búsqueda no indexen completamente el contenido dinámico renderizado en el cliente.
* **Carga inicial**: Los visitantes pueden ver un parpadeo antes de que los componentes se rendericen.
* **Accesibilidad**: Asegúrate de que los lectores de pantalla anuncien los cambios de contenido dinámico.
* **Optimiza los arrays de dependencias**: Incluye solo las dependencias necesarias en `useEffect`.
* **Memoriza operaciones costosas**: Usa `useMemo` o `useCallback` cuando proceda.
* **Reduce los renderizados**: Divide los componentes grandes en otros más pequeños.
* **Carga diferida**: Difiere el renderizado de los componentes complejos hasta que sean necesarios para mejorar el tiempo de carga inicial de la página. Como el entorno sandbox de MDX no admite `React.lazy` ni `import()` dinámico, en su lugar condiciona los componentes pesados a la interacción del usuario o a su visibilidad. Consulta [Diferir el renderizado de componentes pesados](#defer-rendering-for-heavy-components).

<div id="defer-rendering-for-heavy-components">
  ## Diferir el renderizado de componentes pesados
</div>

`React.lazy`, `Suspense` y el `import()` dinámico no están disponibles en el sandbox de MDX. Para obtener el mismo beneficio, renderiza primero un marcador de posición ligero y monta el componente costoso solo después de que el lector lo solicite. Esto mantiene rápida la carga inicial de la página y, aun así, permite que los lectores interactúen con el componente completo.

El ejemplo siguiente mantiene el `ColorGenerator` de la sección anterior sin montar hasta que el lector hace clic en **Load color generator**:

export const LazyColorGenerator = () => {
  const [show, setShow] = useState(false);
  if (!show) {
    return <button onClick={() => setShow(true)} className="px-3 py-1.5 text-sm rounded-lg border border-zinc-950/20 dark:border-white/20 text-zinc-950/80 dark:text-white/80">
        Load color generator
      </button>;
  }
  return <ColorGenerator />;
};

<LazyColorGenerator />

```mdx theme={null}
import { ColorGenerator } from "/snippets/color-generator.jsx"

export const LazyColorGenerator = () => {
  const [show, setShow] = useState(false)

  if (!show) {
    return (
      <button onClick={() => setShow(true)}>
        Load color generator
      </button>
    )
  }

  return <ColorGenerator />
}

<LazyColorGenerator />
```

Para diferir el renderizado hasta que el componente entre en el área visible, sustituye el botón por un `IntersectionObserver` configurado dentro de un `useEffect`. El patrón es el mismo: mantén `show` en `false` hasta que se active el disparador y, entonces, devuelve el componente pesado.
