简体   繁体   中英

React with Redux Saga - How to get data back from axios call

I'm workign with React + Redux + Saga and what I have done so far works perfectly but I need to send data back after a (succeful call).

dispatch to route => sends back an ID => gets the ID

But it doesn't do it.

Route (express server):

axios.post( ... 

return res.json(resAlbum);

My saga:

  function* postSpotifyAlbum(obj) {
      obj = {
        method: "POST",
        url: BASEurl,
        params: {
          token: obj.payload.token,
          albumCode: obj.payload.albumCode,
          albumGenres: obj.payload.albumGenres,
          userCode: obj.payload.userCode,
        },
      };
    
      try {
        const spotifyCategories = yield call(axios, axiosCall(obj));
        yield put({
          type: "POST_SPOTIFY_ALBUMS_SUCCESS",
          payload: JSON.parse(spotifyCategories.data),
        });
        return spotifyCategories.data;
      } catch (error) {
        console.log(error);
        yield put({ type: "POST_SPOTIFY_ALBUMS_ERROR", payload: error });
      }
    } 

Component:

const res = await dispatch(postSpotifyAlbums(obj));

if I console log the res I just get back Obj I just sent.

Question:

Is there any way to send something back from SAGA?

ie res gives me just inserted obj

Or there's no way so then I need to make a second call to the reducer to give me back the just inserted obj?

I repeat, everythig wortks perfectly from the dispatch till the reducer (I ommited to be shorter) I just would like to see if I can expand my SAGA knowledge

With Saga

The returned value from calling dispatch is just the action that you dispatched. Your saga will "take" this action and dispatch additional actions, but that is all considered independent. So your res variable is just the original action postSpotifyAlbums(obj) .

Or there's no way so then I need to make a second call to the reducer to give me back the just inserted obj?

Yes, you would need to have a useSelector in the component that is listening for the posted value.


With Thunk

This is a lot easier to do with because the thunk middleware overrides the default behavior of dispatch . When you dispatch a thunk (an action creator which is a function of dispatch ) then you get back the value that's returned by the thunk.

Any return value from the inner function will be available as the return value of dispatch itself. This is convenient for orchestrating an asynchronous control flow with thunk action creators dispatching each other and returning Promises to wait for each other's completion - docs

action

const postSpotifyAlbums = (payload) => async (dispatch) => {
  dispatch({ type: "POST_SPOTIFY_ALBUMS_PENDING" });
  const args = {
    method: "POST",
    url: BASEurl,
    params: {
      token: payload.token,
      albumCode: payload.albumCode,
      albumGenres: payload.albumGenres,
      userCode: payload.userCode
    }
  };
  try {
    const spotifyCategories = await axios.request(args);
    dispatch({
      type: "POST_SPOTIFY_ALBUMS_SUCCESS",
      payload: spotifyCategories.data
    });
    // we can customize the returned value here to anything!
    return spotifyCategories.data;
  } catch (error) {
    console.log(error);
    dispatch({ type: "POST_SPOTIFY_ALBUMS_ERROR", payload: error });
  }
};

component

const doDispatch = async () => {
  // we get back whatever we specifically returned in the thunk
  const data = await dispatch(postSpotifyAlbums(obj));
  console.log("result", data);
  // we didn't return anything in the error case so it would be undefined
  if (data) {
    // do something with success
  }
};

With createAsyncThunk

includes a helper function createAsyncThunk that automatically creates "pending" "rejected" and "fulfilled" actions in one step. The return value here is the final action that was dispatched.

When dispatched, the thunk will... return a fulfilled promise containing the final dispatched action (either the fulfilled or rejected action object) - docs

action

const postSpotifyAlbums = createAsyncThunk(
  "POST_SPOTIFY_ALBUMS",
  async (payload) => {
    const args = {
      method: "POST",
      url: BASEurl,
      params: {
        token: payload.token,
        albumCode: payload.albumCode,
        albumGenres: payload.albumGenres,
        userCode: payload.userCode
      }
    };
    const spotifyCategories = await axios.request(args);
    return spotifyCategories.data;
  }
);

component

const doDispatch = async () => {
  const action = await dispatch(postSpotifyAlbums(obj));
  console.log("result", action);
  // we could have the success or the failure action
  if ( action.type === postSpotifyAlbums.fulfilled.type) {
    // do something with success
    const data = action.payload;
  } else {
    // do something with error
    const error = action.error;
  }
};

Not Sure if you are still looking for the solution or not, but here's my implementation of the code. Flow goes like this.

  1. We make dispatch request from our component
  2. redux dispatches action called "INITIATE_TRANSACTION".
  3. redux saga intercepts the action and starts making api call based on that
  4. API call is successful and "INITIATE_TRANSACTION_SUCCESS" is dispatched with the api's value.
  5. Now Saga returns value from the axios to the actual calling function from where the step 1 dispatch was called.

Here's the code.

 // store.js import {middleware as thunkMiddleware} from 'redux-saga-thunk'; const middlewares = [thunkMiddleware]; const sagaMiddleware = createSagaMiddleware(); middlewares.push(sagaMiddleware); if (process.env.NODE_ENV === `development`) { const {logger} = require(`redux-logger`); middlewares.push(logger); } const persistConfig = { key: 'root', storage: AsyncStorage, white: ['errors'], }; const persistedReducer = persistReducer(persistConfig, rootReducer); const store = configureStore({ reducer: persistedReducer, middleware: middlewares, devTools: process.env.NODE_ENV === `development`? true: false, }); // Payment.js(or any of your component) const handleBtnClick = async (type, value) => { console.log('btn click type', type, route.params, value); switch (type) { case 'pay': try { let orderId = 'OD_' + moment().format('YYYYMMDDHHmmss'); // Here we are dispatching action "INITIATE_TRANSACTION" to redux saga. // Step 1. const response = await props.initiatePayment({ orderId, orderAmount: 10, orderCurrency: 'INR', }); console.log('response', response.data.cftoken); }catch (error){ console.log('error', error) } break; } // Middleware used is redux-saga-thunk, which basically implements functionality of thunk in redux saga. so meta: {thunk: true} waits for the whole execution to finish and then continues. // Step 2 export const initiatePayment = data => ({ type: INITIATE_TRANSACTION, data, meta: {thunk: true}, }); //Step 3 // Here post is just a custom function for api call, onError and onSuccess are the handler functions and safe is the wrapper method which basically implements try catch logic and handles errors for us without repeating much of the code. // paymentSaga.js function* initiateTransactionSaga({data, meta}) { const response = yield call(post, API.INITIATE_TRANSACTION, data); return response; //Step 4.... } export default function* paymentSaga() { yield takeLatest( INITIATE_TRANSACTION, safe(onError, initiateTransactionSaga, onSuccess), //onSuccess required if you want values to be returned to step 1 ); } // axiosApi.js export async function post(url, data, config = {}) { console.log('url data config', url, data, config); return axiosApi.post(url, {...data}, {...config}).then(response => response.data); } // Sagahelper.js /** * @param handler --- Error handler function. In our case, onError Function * @param saga --- Actual Saga function. in our case Api is called and data is returned * @param success --- success redux action dispatch function -- in our case, if we need to pass response coming from api to the actual calling function( something like props.viewingItem(data)), then pass it here, otherwise leave it blank * @ */ export const safe = ( handler: any = null, saga: any, success: any = null, ...args: any ) => function* (action: any) { try { console.log('action in safe===', action, success); const res1 = yield call(saga, ...args, action); // Success wrapper. if you pass onSuccess method, then only this part will be executed. If you do not want the values to be returned from redux-saga to your component function call, then i suggest you skip it. if (success) { yield call(success, res1, action.type, action.meta); return res1; //This line returns value to the component function( to step 1) } } catch (err) { yield call(handler, ...args, err, action.type, action.meta); } }; export function* onError(err: any, type: any, meta: any) { console.log('type onError', type); yield put({ type: type + '_ERROR', payload: err, error: true, meta, }); // Do something with the error msg. like show alert, etc... return err; } export function* onSuccess(response: any, type: any, meta: any) { console.log('type onError', response); yield put({ type: type + '_SUCCESS', payload: response, error: false, meta, }); // Do something with the success msg. like show alert, etc... return response; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM