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í:
- 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.
- Utiliza
- 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 code
if (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.).
- Cuando se hace
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
- 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 comofilter
):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ónfetchDailyGoalsData
.
- Posee un método
- 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
- No duplicas la lógica de fetching en el hijo.
- El Componente Padre es la “fuente de verdad” para la data.
- 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. - 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 hacedispatch({ 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
- Centraliza la lógica de fetch en un solo lugar. Normalmente, el padre o un hook/custom hook o en Redux (acciones thunks).
- No dupliques llamadas a la API en cada componente que necesite refrescar datos.
- Tras una operación exitosa de CRUD, llama una función del padre (o una acción de Redux) para refrescar la data global.
- 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.