Karan Kumar
Karan Kumar

Reputation: 3186

Redux: Make http call ONLY IF data not available in store?

I do not want to make an http call unless it is actually required:

This is a workaround I have come up with, I am checking the state before making http call

export const fetchOneApi = (id) => async (dispatch, getState) => {
  const docDetails = getState().DocState;

  // tricking redux to not send http request unless actually required
  if (docDetails.docList[id]) {
    return dispatch({
      type: FETCH_DOC_SUCCESS,
      payload: docDetails.docList[id],
    });
  }

  try {
    dispatch({
      type: FETCH_DOC_REQUEST,
    });
    const { data } = await api.get(`/api/${id}`);
    dispatch({
      type: FETCH_DOC_SUCCESS,
      payload: data,
    });
  } catch (error) {
    dispatch({
      type: FETCH_DOC_FAIL,
      payload: error.error,
    });
  }
};

Wondering if there is some redux hook or feature that takes care of this OR atleast a better approach.

Upvotes: 1

Views: 1736

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42228

I've written multiple different custom versions of this functionality for various projects. I toyed with sharing some examples but it's all excessively complicated since I really love to abstract things.

Based on your question, what you are asking for is the createAysncThunk function from redux-toolkit. This function creates an action creator which handles dispatching the pending, fulfilled, and rejected actions at the appropriate times.

There are many ways to customize the behavior of the async thunk. Conditional fetching is described in the docs section "Cancelling Before Execution":

If you need to cancel a thunk before the payload creator is called, you may provide a condition callback as an option after the payload creator. The callback will receive the thunk argument and an object with {getState, extra} as parameters, and use those to decide whether to continue or not. If the execution should be canceled, the condition callback should return a literal false value:

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, thunkAPI) => {
    const response = await userAPI.fetchById(userId)
    return response.data
  },
  {
    condition: (userId, { getState, extra }) => {
      const { users } = getState()
      const fetchStatus = users.requests[userId]
      if (fetchStatus === 'fulfilled' || fetchStatus === 'loading') {
        // Already fetched or in progress, don't need to re-fetch
        return false
      }
    }
  }
)

In your example you are short-circuiting by dispatching a success action with redundant data. The standard behavior for the above code is that no action will be dispatched at all if the fetch condition returns false, which is what we want.

We want to store the pending state in redux in order to prevent duplicate fetches. To do that, your reducer needs to respond to the pending action dispatched by the thunk. You can find out which document was requested by looking at the action.meta.arg property.

// example pending action from fetchUserById(5)
{
  type: "users/fetchByIdStatus/pending",
  meta: {
    arg: 5, // the userId argument
    requestId: "IjNY1OXk4APoVdaYIF8_I",
    requestStatus: "pending"
  }
}

That property exists on all three of the dispatched actions and its value is the argument that you provide when you call your action creator function. In the above example it is the userId which is presumably a string or number, but you can use a keyed object if you need to pass multiple arguments.

Upvotes: 2

Related Questions