Mejor Práctica para Actualizar el DOM en React después de hacer una Petición POST HTTP

A continuación te dejo un tutorial (en español) bastante detallado que explica el antes (cómo se solía hacer) y el después (la manera recomendada) para manejar el fetching de datos y la actualización del estado (ya sea en Redux o en el estado local) tras operaciones de CRUD en React.


1. Contexto

Cuando construimos aplicaciones en React (o con Redux), suele haber un componente padre que:

  • Hace llamadas HTTP GETGETGET para obtener datos.
  • Muestra dichos datos en pantalla.
  • Permite que el usuario realice acciones de CRUD (Create, Read, Update, Delete).

El problema más común aparece cuando necesitamos actualizar la vista tras crear o editar un elemento. Muchas veces, la lógica de obtener nuevamente los datos (los GET o “re-fetch”) termina duplicándose entre distintos componentes. O bien, el componente hijo hace la petición para volver a traer datos, lo que produce código repetido y poca claridad en la estructura.


2. Enfoque Tradicional (No Recomendado)

El enfoque tradicional puede lucir así:

  1. Componente Principal (View o Screen):
    • Utiliza useEffect (o similar) para hacer las peticiones GET a la API:jsCopy codeuseEffect(() => { fetchDatos(); // Llama a un GET y guarda en estado o Redux }, []);
    • Muestra los datos en pantalla.
  2. Componente Hijo (Formulario de Crear/Editar):
    • Cuando se hace submit, ejecuta una petición POST o PUT para crear/actualizar un recurso.
    • También hace las peticiones GET para refrescar la lista, duplicando la misma lógica que ya estaba en el componente principal:jsCopy codeif (ok) { fetchDatos(); // Se vuelve a llamar a GET desde aquí }
    • Este paso duplica la misma lógica de fetchDatos() que ya existía en el padre, y encima el hijo debe conocer todos los endpoints que hay que llamar para refrescar la vista (por ejemplo: topWords, averages, stats, etc.).

Problemas de este Enfoque

  • Duplicación de código: El hijo debe saber cómo refrescar la lista global, mientras que el padre ya hace algo parecido.
  • Responsabilidades confusas: El Componente Hijo (el formulario) termina teniendo la responsabilidad de saber qué datos globales se tienen que actualizar y de qué manera.
  • Mantenibilidad: Si se cambia la forma en la que se fetchan los datos (nuevos endpoints o parámetros), hay que actualizar el padre y también cualquier hijo que repita la lógica.

3. Enfoque Recomendado (“Lifting Data Fetch Logic”)

La mejor práctica consiste en concentrar toda la lógica de obtención y actualización de datos en un solo lugar (normalmente, el componente padre o un custom hook o las acciones de Redux). Luego, el componente padre expone una función de refresco que pueda ser llamada desde el hijo después de una operación exitosa de POST, PUT o DELETE.

Cómo se ve en la práctica

  1. Componente Padre:
    • Posee un método fetchDailyGoalsData (o el nombre que quieras) que hace todas las llamadas GET necesarias para armar la vista y actualiza el estado o el store de Redux.
    • En useEffect, se invoca una sola vez (o cada vez que cambien dependencias como filter):jsCopy codeuseEffect(() => { fetchDailyGoalsData(); }, [filter]);
    • Muestra los datos en pantalla (o pasa los datos a otros subcomponentes).
    • Le pasa al Componente Hijo una prop (por ejemplo, onRefreshData) que apunta a esa misma función fetchDailyGoalsData.
  2. Componente Hijo (el que hace CRUD):
    • Cuando el usuario hace clic en “Guardar” o “Crear”, se llama a la API POST (o PUT, DELETE, etc.).
    • Si la operación es exitosa, no re-implementamos la lógica para obtener datos.
    • En lugar de eso, llamamos onRefreshData() (la prop que vino del padre), lo que re-dispara todas las peticiones GET desde el componente padre.
    • Opcionalmente, cerramos el modal o hacemos un reset del formulario.

Ejemplo Simplificado de Código

Padre:

jsCopy codefunction ParentComponent() {
  // Estado local o Redux
  const [data, setData] = useState([]);
  
  // 1. Función que hace todas las llamadas GET
  const fetchAllData = async () => {
    const result = await fetch('https://api/ejemplo'); // o varias llamadas
    setData(result);
  };
  
  // 2. Llamada inicial
  useEffect(() => {
    fetchAllData();
  }, []);
  
  return (
    <div>
      <ChildForm onRefreshData={fetchAllData} />
      <DataList data={data} />
    </div>
  );
}

Hijo:

jsCopy codefunction ChildForm({ onRefreshData }) {
  const onSubmit = async (formValues) => {
    const response = await fetch('https://api/ejemplo', {
      method: 'POST',
      body: JSON.stringify(formValues),
      headers: { 'Content-Type': 'application/json' },
    });
    
    if (response.ok) {
      // Cerrar modal o mostrar mensaje
      // y luego pedirle al padre que refresque los datos
      onRefreshData();
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* ... campos y botón de submit */}
    </form>
  );
}

Ventajas de este Método

  1. No duplicas la lógica de fetching en el hijo.
  2. El Componente Padre es la “fuente de verdad” para la data.
  3. Mantenerlo es más fácil: si necesitas añadir otro endpoint o un nuevo parámetro, solo actualizas la función fetchAllData en el padre.
  4. El hijo queda ligero: solo crea o edita el recurso, y en caso de éxito, llama a una función que vive en el padre.

4. ¿Qué Pasa si Usamos Redux?

Si utilizas Redux, la idea es la misma; solo que en lugar de usar estado local, usas acciones y reducers:

  • Acción para obtener datos (fetchDataAction), que hace dispatch({ type: 'RECEIVE_DATA', payload: data }).
  • Acción para crear o editar un item (createDataAction), y al completar con éxito, ya sea:
    • actualizas el store con la nueva data, o
    • re-llamas a fetchDataAction() para forzar la recarga.

Ejemplo con Thunks

jsCopy code// dailyGoalActions.js
export const fetchDailyData = () => {
  return async (dispatch, getState) => {
    // fetch a tus endpoints...
    const data = await algo();
    dispatch({ type: 'RECEIVE_DATA', payload: data });
  };
};

export const createDailyMood = (formValues) => {
  return async (dispatch) => {
    const response = await fetch('...', { method: 'POST', body: JSON.stringify(formValues) });
    if (response.ok) {
      // O bien 1) actualizamos store con la nueva data
      dispatch({ type: 'ADD_MOOD_TRACKER', payload: newData });
      
      // O bien 2) re-dispatch para recargar todo
      dispatch(fetchDailyData());
    }
  };
};

De nuevo, la lógica de “cómo se obtienen los datos” vive en una sola función que luego se puede reutilizar cada vez que haga falta refrescar los datos en el store.


5. Conclusión y Recomendaciones

  1. Centraliza la lógica de fetch en un solo lugar. Normalmente, el padre o un hook/custom hook o en Redux (acciones thunks).
  2. No dupliques llamadas a la API en cada componente que necesite refrescar datos.
  3. Tras una operación exitosa de CRUD, llama una función del padre (o una acción de Redux) para refrescar la data global.
  4. Mantén los componentes hijos lo más sencillos posible: que solo se dediquen a la operación concreta y, en caso de éxito, notifiquen al padre.

Siguiendo este patrón, tu aplicación será más fácil de mantener y más clara. Cualquier modificación en la forma de obtener datos (nuevos endpoints, nuevas transformaciones) se hará solamente en un lugar, y todos los componentes que la usen quedarán automáticamente actualizados.

¡Y eso es todo! Con estos pasos y recomendaciones, tendrás un proyecto más escalable, ordenado y fácil de extender en el futuro.