Best Practice for Updating the DOM in React After Making an HTTP POST Request

Below is a fairly detailed tutorial (in Spanish) that explains the before (how it used to be done) and the after (the recommended way) of handling data fetching and state updates (whether in Redux or local state) after CRUD operations in React.


1. Context

When building applications in React (or with Redux), there is usually a parent component that:

  • Makes HTTP GETGETGET calls to fetch data.
  • Displays that data on the screen.
  • Allows the user to perform CRUD actions (Create, Read, Update, Delete).

The most common problem arises when we need to update the view after creating or editing an item. Often, the logic to fetch the data again (the GET or “re-fetch”) ends up being duplicated across different components. Or the child component makes the request to fetch data again, which results in repeated code and unclear structure.


2. Traditional Approach (Not Recommended)

The traditional approach may look like this:

  1. Main Component (View or Screen):
    • Uses useEffect (or similar) to make GET requests to the API:jsCopy codeuseEffect(() => { fetchData(); // Calls a GET and stores in state or Redux }, []);
    • Displays the data on screen.
  2. Child Component (Create/Edit Form):
    • When submitting, executes a POST or PUT request to create/update a resource.
    • Also makes GET requests to refresh the list, duplicating the same logic already present in the main component:jsCopy codeif (ok) { fetchData(); // Calls GET again from here }
    • This step duplicates the same fetchData() logic that already existed in the parent, and on top of that the child needs to know all the endpoints that must be called to refresh the view (for example: topWords, averages, stats, etc.).

Problems with this Approach

  • Code duplication: The child must know how to refresh the global list, while the parent already does something similar.
  • Confused responsibilities: The Child Component (the form) ends up being responsible for knowing which global data must be updated and how.
  • Maintainability: If the way data is fetched changes (new endpoints or parameters), both the parent and any child that repeats the logic must be updated.

3. Recommended Approach (“Lifting Data Fetch Logic”)

The best practice is to concentrate all the logic for fetching and updating data in one place (usually the parent component, a custom hook, or Redux actions). Then, the parent component exposes a refresh function that can be called from the child after a successful POST, PUT, or DELETE operation.

What it looks like in practice

  1. Parent Component:
    • Has a method fetchDailyGoalsData (or any name you want) that makes all the necessary GET calls to build the view and updates state or the Redux store.
    • In useEffect, it is invoked once (or whenever dependencies like filter change):jsCopy codeuseEffect(() => { fetchDailyGoalsData(); }, [filter]);
    • Displays the data on screen (or passes the data to other subcomponents).
    • Passes to the Child Component a prop (for example, onRefreshData) pointing to that same fetchDailyGoalsData function.
  2. Child Component (the one doing CRUD):
    • When the user clicks “Save” or “Create”, it calls the API with POST (or PUT, DELETE, etc.).
    • If the operation is successful, we do not re-implement the data fetching logic.
    • Instead, we call onRefreshData() (the prop from the parent), which triggers all the GET requests from the parent component.
    • Optionally, close the modal or reset the form.

Simplified Code Example

Parent:

jsCopy codefunction ParentComponent() {
  // Local state or Redux
  const [data, setData] = useState([]);
  
  // 1. Function that makes all GET calls
  const fetchAllData = async () => {
    const result = await fetch('https://api/example'); // or multiple calls
    setData(result);
  };
  
  // 2. Initial call
  useEffect(() => {
    fetchAllData();
  }, []);
  
  return (
    <div>
      <ChildForm onRefreshData={fetchAllData} />
      <DataList data={data} />
    </div>
  );
}

Child:

jsCopy codefunction ChildForm({ onRefreshData }) {
  const onSubmit = async (formValues) => {
    const response = await fetch('https://api/example', {
      method: 'POST',
      body: JSON.stringify(formValues),
      headers: { 'Content-Type': 'application/json' },
    });
    
    if (response.ok) {
      // Close modal or show message
      // then ask parent to refresh data
      onRefreshData();
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* ... fields and submit button */}
    </form>
  );
}

Advantages of this Method

  1. You don’t duplicate fetching logic in the child.
  2. The Parent Component is the “source of truth” for the data.
  3. It is easier to maintain: if you need to add another endpoint or parameter, you only update the fetchAllData function in the parent.
  4. The child stays lightweight: it only creates or edits the resource, and on success calls a function living in the parent.

4. What if We Use Redux?

If you use Redux, the idea is the same; only instead of local state, you use actions and reducers:

  • Action to fetch data (fetchDataAction), which does dispatch({ type: 'RECEIVE_DATA', payload: data }).
  • Action to create or edit an item (createDataAction), and on success either:
    • update the store with the new data, or
    • re-call fetchDataAction() to force reload.

Example with Thunks

jsCopy code// dailyGoalActions.js
export const fetchDailyData = () => {
  return async (dispatch, getState) => {
    // fetch your endpoints...
    const data = await something();
    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) {
      // Either 1) update store with new data
      dispatch({ type


: 'ADD\_DAILY\_MOOD', payload: await response.json() });

```
  // OR 2) refresh full data
  dispatch(fetchDailyData());
}
```

};
}; 

In your React component, just dispatch createDailyMood. The store updates, and the UI reacts accordingly.


5. Summary

  • Keep the data-fetching logic centralized (parent or Redux).
  • Child components trigger refreshes by calling a prop/function from the parent.
  • This avoids code duplication, improves maintainability, and keeps components with clear responsibilities.
  • If using Redux, use thunk actions to fetch and update, dispatching refresh actions as needed.