martes, 10 de marzo de 2026

Renderizar en React a partir de un objeto que puede cambiar obtenido por fetch

 Tengo un componente React que renderiza los datos de un objeto. Este objeto se carga a partir de un fetch, pero también es actualizado mediante un manejador de eventos. ¿Cómo hago para que el componente React se re-renderice por completo cuando cambia este objeto? Además las modificaciones realizadas sobre el objeto pueden no existir en el objeto original, sino crearse un nuevo objeto a partir del original y aplicársele las modificaciones.

En React, el re-renderizado depende de que el estado o las props cambien de referencia. Si tenés un objeto que se carga con fetch y luego se modifica con un manejador de eventos, lo importante es no mutar directamente el objeto, sino crear uno nuevo y actualizar el estado con esa nueva referencia.

📌 Estrategia correcta

  1. Guardá el objeto en un state con useState.

  2. Cada vez que lo modifiques, generá un nuevo objeto (spread, Object.assign, etc.) en lugar de mutar el existente.

  3. Llamá al setter (setData) con ese nuevo objeto.

  4. React detecta el cambio de referencia y re-renderiza el componente.

📌 Ejemplo

tsx
import { useState, useEffect } from "react";

function Odontograma() {
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    fetch("/api/paciente")
      .then(res => res.json())
      .then(obj => setData(obj));
  }, []);

  const handleUpdate = () => {
    // Crear un nuevo objeto a partir del original
    setData(prev => ({
      ...prev,
      nuevaPropiedad: "valor"
    }));
  };

  if (!data) return <p>Cargando...</p>;

  return (
    <div>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <button onClick={handleUpdate}>Actualizar</button>
    </div>
  );
}

📌 Puntos clave

  • Nunca mutar: data.prop = "nuevo" no dispara re-render.

  • Siempre crear nueva referencia: setData({...data, prop: "nuevo"}).

  • Si las modificaciones son profundas, podés usar librerías como Immer para simplificar la creación de copias inmutables.

En tu caso, como las modificaciones pueden generar un objeto nuevo a partir del original, lo ideal es siempre trabajar con copias inmutables y actualizar el estado con esa copia. Así garantizás que React re-renderice el componente completo.

¿Querés que te arme un checklist práctico de buenas prácticas para manejar objetos complejos en el estado de React (como tu odontograma)?


SÍ.

Aquí tenés un checklist práctico para manejar objetos complejos en el estado de React, como tu odontograma, asegurando que el componente se re-renderice correctamente:

✅ Checklist de buenas prácticas

  • Usar useState o useReducer Guarda el objeto en un hook de estado. No lo mantengas como variable suelta, porque React no detecta cambios ahí.

  • No mutar directamente el objeto Evitá hacer data.prop = "nuevo". Eso no cambia la referencia y React no re-renderiza.

  • Crear siempre un nuevo objeto Usá el spread operator o utilidades como Object.assign:

    ts
    setData(prev => ({ ...prev, prop: "nuevo" }));
    
  • Actualizar arrays de forma inmutable Para agregar o quitar elementos:

    ts
    setData(prev => ({ ...prev, items: [...prev.items, nuevoItem] }));
    
  • Usar librerías de inmutabilidad si es complejo Con Immer podés escribir código “como si mutaras”, pero se generan copias inmutables automáticamente.

  • Evitar estados demasiado anidados Si el objeto es muy grande, considerá dividirlo en varios estados más pequeños o usar un useReducer para mayor control.

  • Sincronizar con fetch y eventos

    • Al cargar datos: setData(obj)

    • Al modificar: setData(prev => nuevaCopia) Así mantenés consistencia entre datos iniciales y cambios del usuario.

  • Re-render garantizado Cada vez que el setter recibe un objeto nuevo (referencia distinta), React re-renderiza el componente.

Este enfoque asegura que tu odontograma se actualice de forma predecible y eficiente, incluso cuando las modificaciones generan un objeto nuevo a partir del original.

¿Querés que te arme un ejemplo completo con useReducer, mostrando cómo manejar un odontograma con acciones como agregar patología, eliminar pieza o actualizar estado de un diente?


SÍ.

Perfecto, Gabriel. Te armo un ejemplo completo usando useReducer para manejar el estado de un odontograma con acciones claras como agregar patología, eliminar pieza y actualizar estado de un diente. Esto te da control total y evita problemas de mutabilidad.

📌 Ejemplo con useReducer

tsx
import React, { useReducer } from "react";

// Definimos el tipo de pieza dental
interface Pieza {
  id: string;
  patologias: string[];
  estado: string;
}

// Estado global del odontograma
interface OdontogramaState {
  piezas: Record<string, Pieza>;
}

// Acciones posibles
type Action =
  | { type: "AGREGAR_PATOLOGIA"; piezaId: string; patologia: string }
  | { type: "ELIMINAR_PIEZA"; piezaId: string }
  | { type: "ACTUALIZAR_ESTADO"; piezaId: string; estado: string };

// Reducer
function odontogramaReducer(state: OdontogramaState, action: Action): OdontogramaState {
  switch (action.type) {
    case "AGREGAR_PATOLOGIA":
      return {
        ...state,
        piezas: {
          ...state.piezas,
          [action.piezaId]: {
            ...state.piezas[action.piezaId],
            patologias: [...state.piezas[action.piezaId].patologias, action.patologia],
          },
        },
      };

    case "ELIMINAR_PIEZA":
      const { [action.piezaId]: _, ...rest } = state.piezas;
      return { ...state, piezas: rest };

    case "ACTUALIZAR_ESTADO":
      return {
        ...state,
        piezas: {
          ...state.piezas,
          [action.piezaId]: {
            ...state.piezas[action.piezaId],
            estado: action.estado,
          },
        },
      };

    default:
      return state;
  }
}

// Componente principal
export default function Odontograma() {
  const [state, dispatch] = useReducer(odontogramaReducer, {
    piezas: {
      "11": { id: "11", patologias: [], estado: "sano" },
      "12": { id: "12", patologias: [], estado: "sano" },
    },
  });

  return (
    <div>
      <h2>Odontograma</h2>
      {Object.values(state.piezas).map(pieza => (
        <div key={pieza.id}>
          <strong>Pieza {pieza.id}</strong> - Estado: {pieza.estado}
          <br />
          Patologías: {pieza.patologias.join(", ") || "Ninguna"}
          <br />
          <button onClick={() => dispatch({ type: "AGREGAR_PATOLOGIA", piezaId: pieza.id, patologia: "Diastema" })}>
            Agregar Diastema
          </button>
          <button onClick={() => dispatch({ type: "ACTUALIZAR_ESTADO", piezaId: pieza.id, estado: "extracción pendiente" })}>
            Cambiar estado
          </button>
          <button onClick={() => dispatch({ type: "ELIMINAR_PIEZA", piezaId: pieza.id })}>
            Eliminar pieza
          </button>
        </div>
      ))}
    </div>
  );
}

✅ Ventajas de este enfoque

  • Inmutabilidad garantizada: cada acción crea un nuevo estado.

  • Escalabilidad: podés agregar más acciones sin romper la lógica.

  • Claridad: cada modificación está centralizada en el reducer.

  • Re-render seguro: React detecta el nuevo estado y actualiza la UI.

¿Querés que te prepare también un diagrama visual del flujo de acciones (estado inicial → acción → nuevo estado → render) para que lo uses como recurso didáctico en tus presentaciones o documentación?


Cuando un objeto que además es un estado (useState) tiene una propiedad que es un array ¿Puedo actualizar el array in-place mientras que al objeto lo clono para el setState?

En React, no conviene actualizar un array in-place aunque clones el objeto padre. La razón es que React detecta cambios por referencia, y si el array mantiene la misma referencia interna, el re-render puede no ocurrir como esperás o puede generar inconsistencias.

📌 Ejemplo incorrecto (mutación in-place)

tsx
setData(prev => {
  prev.items.push("nuevo"); // mutación directa
  return { ...prev };       // clono el objeto, pero el array sigue siendo el mismo
});

👉 El objeto padre cambia de referencia, pero el array no. Esto puede causar problemas si otras partes del código dependen de que el array sea nuevo.

📌 Ejemplo correcto (inmutabilidad total)

tsx
setData(prev => ({
  ...prev,
  items: [...prev.items, "nuevo"] // nuevo array, nueva referencia
}));

👉 Tanto el objeto como el array cambian de referencia, garantizando que React re-renderice correctamente.

✅ Recomendación

  • Siempre clonar también los arrays cuando actualices un objeto en el estado.

  • Usá el spread operator ([...]) o métodos inmutables (map, filter, concat).

  • Si el objeto es muy complejo, podés usar Immer para simplificar la creación de copias inmutables.

En resumen: no es seguro mutar el array in-place aunque clones el objeto. Lo correcto es crear un nuevo objeto y un nuevo array en cada actualización.

¿Querés que te arme un mini checklist de patrones inmutables para arrays en React, para que lo tengas como referencia rápida en tu trabajo con el odontograma?


Sí.

Aquí tenés un mini checklist de patrones inmutables para arrays en React, pensado como referencia rápida para tu trabajo con el odontograma:

✅ Checklist de arrays inmutables en React

  • Agregar elemento

    ts
    setData(prev => ({ ...prev, items: [...prev.items, nuevoItem] }));
    
  • Eliminar elemento

    ts
    setData(prev => ({ ...prev, items: prev.items.filter(x => x.id !== id) }));
    
  • Actualizar elemento

    ts
    setData(prev => ({
      ...prev,
      items: prev.items.map(x => x.id === id ? { ...x, prop: "nuevo" } : x)
    }));
    
  • Insertar en posición específica

    ts
    setData(prev => ({
      ...prev,
      items: [
        ...prev.items.slice(0, index),
        nuevoItem,
        ...prev.items.slice(index)
      ]
    }));
    
  • Reordenar elementos

    ts
    setData(prev => ({
      ...prev,
      items: [...prev.items].sort((a, b) => a.valor - b.valor)
    }));
    

📌 Reglas de oro

  • Nunca uses push, splice, sort o reverse directamente sobre el array del estado.

  • Siempre devolvé un nuevo array con map, filter, concat, slice o el spread operator.

  • Si el estado es muy complejo, apoyate en Immer para simplificar la lógica.

Este checklist te asegura que cada cambio en arrays dentro de objetos de estado sea inmutable y seguro, garantizando el re-render correcto de tu componente.

¿Querés que te arme también un diagrama visual de flujo mostrando cómo pasa el estado de array original → acción → nuevo array → re-render?

No hay comentarios: