Reputation: 3186
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
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, thecondition
callback should return a literalfalse
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