# 5. Buenas Prácticas en React

## Componentes de Función: La preferencia del equipo

No hay razón para seguir escribiendo componentes de clase, son una tecnología vieja que evitamos en nuestras aplicaciones. Los componentes de función, junto con los _Hooks_, ofrecen una forma más simple y directa de manejar el estado, la sincronización y los efectos secundarios, lo que resulta en un código más limpio y fácil de depurar.

## Estructura y Organización

Los componentes deben tener una única responsabilidad. Los que orquestan, renderizan y hacen malabares al mismo tiempo, fácilmente se convierten en un nido de _bugs_, que fácilmente crean condiciones de carrera o bucles de renderizado.

la estructura de los componentes en el archivo debe ser la siguiente:

```tsx
import { useContext, useMemo, useState, useCallback } from "react";
import { theAppContext } from "AppContext"; 

// 1. Declaracion de tipos locales y constantes importantes para el componente o que determinan el funcionamiento del componente
interface MyComponentProps {
  data: number[];
}

const demonNames: Map<number, string> = new Map([
  [1, "Asmodeus"],
  [2, "Belial"],
  [3, "Baphomet"],
  [4, "Belzebub"],
  // ...
]);

// 2. Declaracion del componente
export function MyComponent({ data }: MyComponentProps) {
  // a. Hooks, constantes y callbacks memoizados
  const [myState, setMyState] = useState<string>("Some Shit");
  const myContext = useContext(theAppContext);

  const expensiveCalculation = useMemo(() => {
    return data.length * myContext.counter;
  }, [data, myContext.counter]);

  // b. Lógica interna, callbacks no memoizados y funciones que dependan del estado, props o hooks
  const handleClick = () => {
    setMyState("This is the new Shit");
  };

  // c. Variables derivadas para el render
  const otherShit = myState !== "Some Shit";
  const baphyApproved = otherShit && expensiveCalculation === 666;

  // d. Render
  return (
    <div>
      <p>{myState}</p>
      {otherShit && <p>{expensiveCalculation}</p>}
      {baphyApproved && <p>Beast time!!!</p>}
      {baphyApproved &&
        data.map((id) => (
          <p key={id}>{idToDemonName(id)}</p>
        ))}
      <button onClick={handleClick}>Change this shit</button>
    </div>
  );
}

// 3. helpers y funciones relacionadas con el componente que no dependen del estado, props o hooks de este
function idToDemonName(id: number): string {
  const name = demonNames.get(id) ?? "Unknown Demon";
  return `${id} is ${name}...`;
}
```

### El `return` claro y el chocolate oscuro

El JSX no es entorno amable para leer, por lo que el `return` debe ser lo más limpio posible. Toda la lógica compleja como los cálculos de estado, la preparación de datos, las condiciones de renderizado y los _handlers_ debe hacerse antes del `return`.

Por una cuestión de claridad y consistencia, evitamos los `early returns` en el renderizado de componentes. En su lugar, centralizamos toda la lógica de lo que se renderiza al final. Sin embargo, ojo, los `early returns` en otras funciones están totalmente permitidos, recomendados, aplaudidos y premiados con carita feliz. A esto nos referimos con `early returns` en el renderizado de componentes:

```tsx
// Componente malo, pao pao, eso no se hace
function MyBadComponent() {
  // la logica del componente, hooks and shit...
  
  if (withoutBaphometInYourHeart) {
    return (
      <h1>Un retorno precoz</h1>
    )
  }

  // otras vainas
  return (
    <h1>El retorno final del componente</h1>
  )
}

// Componente bueno y con carita feliz
function MyGoodComponent() {
  // Toda la lógica del componente y la vaina

  return (
    {iHaveBaphometInMyHeart
      ? <h1>Soy lo que sale si Baphy está en mi corazón</h1>
      : <h1>Aunque no tienes a Baphy en tu corazón, el componente está bien</h1>
    }
  )
}
```

El `return` debe contener únicamente:

- Expresiones simples (ej., `{condición && <Componente />}`).
- Listas (`.map()`).

## Props: Cómo pasar y gestionar propiedades

Una de las mayores ventajas de usar TypeScript es que podemos definir la "forma" de las _props_ explícitamente sin usar prop-types, lo que nos permite saber exactamente qué tipo de datos estamos recibiendo y enviando, y evitar errores en tiempo de compilación. Siempre tipamos los props. Esta práctica nos asegura que los datos que pasan entre componentes sean consistentes y predecibles.

Un punto importante es evitar el uso de `React.FC` y similares que sirven para tipar componentes. Aunque parece práctico, puede causar problemas con el tipado de la propiedad `children`, siempre la convierte en opcional, sin importar que la hayamos definido como obligatoria o que el componente no la necesite... lo que podría generar confusiones y _bugs_ difíciles de rastrear.

## Manejo de Estados: Estrategias claras

Di no a los estados derivados; en lugar de almacenar una variable en el estado que se puede calcular a partir de otros, realizamos el cálculo directamente en el cuerpo del componente, antes del renderizado.

Para manejar el estado, usamos `useState` para estados simples, como booleanos, cadenas, números, objetos, etc. Sin embargo, para estados más complejos que involucran múltiples transiciones o modificaciones, preferimos usar `useReducer`, ya que centraliza la lógica de actualización del estado y la hace más predecible y testeable.

## Sincronización (`useEffect`)

Dejemos de pensar en `useEffect` como una simple herramienta para efectos secundarios, tomémoslo como un mecanismo para la sincronización del componente con sistemas externos (fetch, localstorage, etc). Como se sugiere [You might not need an effect](https://react.dev/learn/you-might-not-need-an-effect), a menudo no se necesita un efecto, por lo que es vital usarlo solo cuando es necesario y para lo que fue creado.

Para mantener la claridad y la consistencia, haz que cada `useEffect` se encargue de un solo aspecto. Si un componente necesita múltiples `useEffect`, esto podría ser un indicio de que está asumiendo demasiadas responsabilidades y que la arquitectura debe ser revisada.

Y para cierre... del useEffect... si un efecto crea una suscripción o un temporizador, asegúrate de retornar la función de limpieza (_cleanup_) para evitar fugas de memoria y otros chascarrillos.

## useMemo y useCallback

No agarrarlos por default ni meterlos hasta en la sopa. Estos hooks tienen un costo de memoria y de complejidad que no siempre vale la pena. Úsalos solo cuando sea necesario, parte de esta idea: empezar sin ellos y agrégalos solo cuando veas un problema de rendimiento o un re-render innecesario que impacte el rendimiento o la UX.

## Código ordenado

Parte de nuestra estrategia para mantener el código ordenado y fácil de depurar, echamos mano de useContext y custom hooks:

**`useContext`** es para evitar el "prop-drilling" y contiene datos globales para todos los hijos, como el tema de la aplicación, la información del usuario, el estado de autenticación o pasar un reducer para que controla el estado del padre.

**Custom Hooks** para encapsular lógica compleja y reutilizable (como un `useFetcher`), darles una sola responsabilidad, siguiendo el principio de "un solo propósito".

## Fragmentos y Portales

Los **Fragmentos** son un mecanismo de React para agrupar varios elementos hijos sin la necesidad de agregar un nodo extra al DOM, porque la cantidad de nodos en el DOM importa! y es útil para mantener una estructura semántica y ligera del documento. Usar siempre la sintaxis corta `<>{/*contenido del render*/}</>`.

`createPortal` nos permiten renderizar un componente fuera del árbol DOM del componente padre. Son muy útiles para crear elementos como modales, _tooltips_ o notificaciones, que necesitan renderizarse en la parte superior del árbol DOM y nos ayudan a evitar problemas de superposición con otros elementos.

## Handlers (Funciones para manejar eventos de usuario)

Para mantener el código claro, un handler debe tener una sola responsabilidad, como hacer una petición a la API o actualizar un estado. Siempre los nombramos con el prefijo handle (ej., handleClick, handleSubmit). Si un handler depende del estado o de las props, lo definimos dentro del componente; si no, lo movemos fuera del componente para que sea más fácil de reutilizar y probar, si solo es usado en este componente, puede dejarse en el mismo archivo, de lo contrario, debe moverse al lugar que la arquitectura de la función haya designado para los helpers y utils.

## Seguridad y Buenas Prácticas

- **Sanitización de datos**: Asegúrate de que los datos externos se limpien antes de ser renderizados en JSX para prevenir ataques de _cross-site scripting_.

- **Manejo seguro de dependencias del package.json**: La gestión de dependencias es un proceso delicado. Las actualizaciones automáticas solo deben aplicarse para _hotfixes_ y parches menores. Las actualizaciones de versión y los cambios importantes deben hacerse bajo supervisión al menos una vez al año.

- **¡No uses `dangerouslySetInnerHTML`!**: Esta propiedad es peligrosa y debe evitarse. Si es absolutamente necesario, su uso debe estar justificado y el contenido debe ser sanitizado de antemano.

## Manejo de errores

### Manejo de Errores Asíncronos

Cada llamada a una API u operación asíncrona debe estar envuelta en un bloque try-catch o un encadenado `.then().catch()` apropiado, y los errores deben ser manejados de manera uniforme en toda la aplicación.

Una estrategia son los estados de error en hooks personalizados que permiten centralizar la lógica de manejo de errores y reutilizarla en múltiples componentes. Lo que ayuda con la consistencia en cómo se manejan los errores en toda la aplicación y facilita el mantenimiento del código.

```tsx
import { useState, useEffect } from 'react';

function useFetchData(url: string) {
  const [data, setData] = useState<any>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('No el servidor no retornó la información solicitada.');
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError('Ocurrió un error al cargar la información.');
      }
    };
    fetchData();
  }, [url]);

  return { data, error };
}
```

### Validación y Tipos

No podemos confiar en que los datos externos a la aplicación, como los de las APIs, tengan la estructura que esperamos. Por eso, la validación de los datos antes de usarlos en los componentes es clave. Esta validación debe ser explícita y generar errores informativos que nos ayuden a identificar problemas.

```tsx
// Definimos el tipo de dato que esperamos
interface User {
  id: number;
  name: string;
}

// Usamos una función de validación con una "type guard"
const isUser = (data: unknown): data is User => {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data && typeof data.id === 'number' &&
    'name' in data && typeof data.name === 'string'
  );
};

function MyComponent() {
  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch('/api/user/1');
        
        if (!res.ok) {
          throw new Error(
            `Error en la respuesta de la API: ${res.status}`
          );
        }
        
        const rawData = await res.json();
        if (!isUser(rawData)) {
          throw new Error('La API no devolvió la estructura esperada.');
        }
        
        // Usamos el dato validado de forma segura
        console.log(rawData.name);
      } catch (error) {
        // Manejamos el error de forma centralizada
        console.error(error);
      }
    };
    
    fetchData();
  }, []);
  
  // lo demás del componente
}
```

### Experiencia de Usuario (UX)

La forma en que se comunican los errores es tan importante como la forma en que se manejan. La meta es que incluso cuando las app se rompa, el usuario se sienta en control.

**Interfaces de reserva (_Fallback UI_)**: En lugar de mostrar una pantalla en blanco o un mensaje técnico que el usuario sienta escrito en arameo, debemos proporcionar información clara sobre lo que pasó en una interfaz de reserva.

```tsx
// Cuando el componente falla, se muestra un mensaje claro.
const UserProfile = ({ user }) => {
  // ...
  return !user ? (
    <p>Lo sentimos, no pudimos cargar tu perfil.</p>
  ) : (
    <h1>{user.name}</h1>
    {/*...*/}
  ) 
};
```

**Recuperación**: Después de un fallo, debemos ofrecer formas claras de recuperación. Botones de "reintentar" o instrucciones para volver a la página de inicio son un buen ejemplo de esto. También, para evitar la sensación de que la aplicación está rota, usamos _skeleton screens_ o _spinners_ para indicar los estados de carga. Esto crea una experiencia fluida y profesional.

```tsx
// El botón permite al usuario volver a intentar la operación.
const FetchDataButton = ({ onRetry }) => {
  const [error, setError] = useState(false);

  // ... lógica para manejar el error

  return error ? (
    <div>
      <p>Ocurrió un error al cargar los datos.</p>
      <button onClick={onRetry}>Reintentar</button>
    </div>
  ) : (
    {/*...*/}
  );
};
```

---
[Volver al menú principal](../frontend-structure.md)
