mercredi 13 octobre 2021

React Saga method calls certain Redux method - but is it possible to decouple this hard-coded link and combine individual calls using await?

I have the code that is effectively similar to https://newbedev.com/fetch-api-data-using-redux-redux-saga-and-axios-code-example and which uses React-Saga(Axios)-Redux. This code has one naming error - it stands:

export default function* userSendAll() {
  yield takeLatest(REQUEST_API_DATA, getApiData)
}

but the correct call is (one should call existing userReceiveAll instead of non-existing getApiData):

export default function* userSendAll() {
  yield takeLatest(REQUEST_API_DATA, userReceiveAll)
}

So, back to my question after this correction. The effective flow of execution is as follows (and it is very idiomatic solution, almost the design pattern):

[1.] React component initiates the call to retrieve the external data - effectively REQUEST_API_DATA action is created and forward to Sage for capture and processing:

componentDidMount() {
    this.props.fetchAll()
}
...
const mapDispatchToProps = (dispatch) => ({ fetchAll: () => dispatch(requestApiData()) })
...
export const requestApiData = () => ({ type: REQUEST_API_DATA })

[2.] Saga captures the action, processes it using axios and raises new action that should be processed by Redux Reducer (note correction in naming - use of userReceiveAll):

export default function* userSendAll() {
  yield takeLatest(REQUEST_API_DATA, userReceiveAll)
}
...
function* userReceiveAll(action) {
  ...
    const { data } = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users')
    yield put({ type: RECEIVE_API_DATA, payload: { users: data } })
  ...
}

The last function call contains the essence of my question - it call Axios, get data from Axios and immediatly (in hard-coded fashion) transmits them to Redux Reducer for putting into store which is done by the code:

export default (state = {}, { type, payload }) => {
  switch (type) {
    case RECEIVE_API_DATA:
      return payload.users
    default:
      return state
  }
}

After the update of the store content React does its work automatically further and render the retrieved data on a screen (puts inside HTML).

My question concerts the hard-coded part:

function* userReceiveAll(action) {
  ...
    const { data } = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users')
    yield put({ type: RECEIVE_API_DATA, payload: { users: data } })
  ...
}

and the code that initiates all this cascade of actions:

componentDidMount() {
    this.props.fetchAll()
}

I don't feel easy about the solution that combines the data fetch and data consumption (sending to the specific variable in the Redux store) in hard-coded and immutable way.

We can have the followin (Object-oriented) scenarion: we can have Axios service/Saga method that fetches the Article object. But Redux store can store the retrievied Article in 4 different object hierarchies: SaleItem, SaleOrderItem, Purchase, PurchaseOrderItem. Now, I would like to have the following code in my React Sale component:

handleAddGoodClick() {
    this.props.fetchArticle({id: 5}).addGoodToSale().recalculateSale(); //addGoodToSale can call the ACTION that passes data to Reducer SaleItem  
}

and the following code in my React Purchase component:

handleAddGoodClick() {
    this.props.fetchArticle({id: 5}).addGoodToPurchase().recalculatePurchase(); //addGoodToPurchase can call the ACTION that passes data to Reducer PurchaseItem  
}

Where recalculateSale() and recalculatePurhase() can further be Saga-Axios calling methods, that invokes external services using Axios.

Is it possible to this in this Saga/Axios/Redux setting? I understand that I can do such chaining with the async functions that returns Promises and that passes the result of one call as the argument to the next chained async function. But such chaining works for clearly defined async function.

Here I have the bunch of mystically defined functions about which I have not yet deciphered which is async function and which is not: e.g.

const mapDispatchToProps = (dispatch) => ({ fetchAll: () => dispatch(requestApiData()) })

export const requestApiData = () => ({ type: REQUEST_API_DATA })

who knows whether requestApiData (as it stands) is async function or not? The same is with the call:

yield put({ type: RECEIVE_API_DATA, payload: { users: data } })

can it be promisified into async function which returns promise and made available for the use from React component as, e.g., addGoodToSale() or as addGoodToPurchase()?

Of course. I can stay with the hard coded solution and pass additional argument to fetchAll (e.g. fetchType: Sale, SaleOrder, Purchase, PurchaseOrder) and use this type in userReceiveAll:

const { data } = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users')
switch(fetchType) { 
  ftSale: { yield put({ type: SAVE_ARTICLE_IN_SALE_ITEM, payload: { users: data } }) }
  ftPurchase: { yield put({ type: SAVE_ARTICLE_IN_PURCHASE_ITEM, payload: { users: data } }) }
  ...
}

But it is very bad solution, because there are recalculate procedures based on each type afterwards and the code is not maintainable and module.

So - my question is:

  • Which functions of this code snippet are and are not async function?
  • Can SagaAxios call be decoupled from Redux-Reducer call in separate async function and can these separate async functions be chained together (or called "sequentially" with await) in one call from the proper React component?

This is pretty common use case in React DB applications and I wonder why there is no design pattern already to handle chain of calls.

Aucun commentaire:

Enregistrer un commentaire