reactjs - Reaccionar con Redux Saga - Cómo recuperar datos de una llamada de axios

CorePress2024-01-24  10

Estoy trabajando con React + Redux + Saga y lo que he hecho hasta ahora funciona perfectamente, pero necesito enviar datos después de una (llamada exitosa).

enviar a ruta => devuelve un ID => obtiene la identificación

Pero no lo hace.

Ruta (servidor express):

axios.post( ... 

return res.json(resAlbum);

Mi 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 });
      }
    } 

Componente:

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

Si registro la resolución en la consola, obtengo el objeto que acabo de enviar.

Pregunta:

¿Hay alguna forma de enviar algo desde SAGA?

es decir res me da el obj recién insertado

¿O no hay manera y entonces necesito hacer una segunda llamada al reductor para que me devuelva el objeto que acabo de insertar?

Repito, todo funciona perfectamente desde el envío hasta el reductor (omití ser más corto) Solo me gustaría ver si puedo ampliar mis conocimientos de SAGA.compromiso

Sé que ya respondí esta pregunta antes. Es fácil hacer esto con redux-thunk pero con redux-saga el valor devuelto es solo la primera acción que realiza. El resto de acciones que se desencadenan en tu saga como consecuencia de esto son independientes.

Linda Paiste

27/03/2021 a las 17:58



------------------------------------

con saga

El valor devuelto al llamar al envío es solo la acción que envió. Tu saga "tomará" esta acción y enviar acciones adicionales, pero todo eso se considera independiente. Entonces tu variable res es solo la acción original postSpotifyAlbums(obj).

¿O no hay manera y entonces necesito hacer una segunda llamada al reductor para que me devuelva el objeto que acabo de insertar?

Sí, necesitaría tener un useSelector en el componente que está escuchando el valor publicado.

con golpe

Esto es mucho más fácil de hacer con redux-thunk porque el middleware del procesador anula el comportamiento predeterminado de despacho. Cuando envías un procesador (un creador de acciones que es una función del envío), obtienes el valor devuelto por tpensó.

Cualquier valor de retorno de la función interna estará disponible como valor de retorno del envío mismo. Esto es conveniente para orquestar un flujo de control asincrónico en el que los creadores de acciones de procesador se envían entre sí y devuelven promesas para esperar a que los demás completen - docs

acción

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 });
  }
};

componente

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
  }
};
Con createAsyncThunk

redux-toolkit incluye una función auxiliar createAsyncThunk que crea automáticamente archivos "pendientes". "rechazado" y "cumplido" acciones en un solo paso. El valor de retorno aquí es la acción final que se envió.

Cuando se envía, el procesador... devolverá una promesa cumplida que contiene la acción final enviada (ya sea el objeto de acción cumplido o rechazado) - docs

acción

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;
  }
);

componente

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;
  }
};

3

Esta es una gran respuesta. Nunca probé Saga. Lo intentaría.

- Ajeet Shah

28 de marzo de 2021 a las 0:37

1

La saga @AjeetShah es muy poderosa pero también es un dolor de cabeza. Para la mayoría de las aplicaciones probablemente sea excesivo.

Linda Paiste

28 de marzo de 2021 a las 1:36

1

Muchas gracias por la claridad. Guardé tu respuesta de Thunk para próximos usos porque no conocía la herramienta. Sí, Saga definitivamente es excesivo para la mayoría de los usos y también para este. Este proyecto es un gran entorno de pruebas, así que decidí probar con Saga y sí, es una verdadera molestia.

Marco Disco

28 de marzo de 2021 a las 9:06



------------------------------------

No estoy seguro si todavía estás buscandog para la solución o no, pero aquí está mi implementación del código. El flujo es así.

Realizamos solicitud de despacho desde nuestro componente. redux envía una acción llamada "INITIATE_TRANSACTION". redux saga intercepta la acción y comienza a realizar una llamada a la API basada en eso La llamada API es exitosa y "INITIATE_TRANSACTION_SUCCESS" se envía con el valor de la API. Ahora Saga devuelve el valor de los axios a la función de llamada real desde donde se llamó el envío del paso 1.

Aquí está el código.

// 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;
}

Puedes encontrar información detallada aquí. https://medium.com/@dhavaljavia.p/redux-saga-return-values-from-saga-to-your-component-using-redux-saga-thunk-17ad6e9e81ef

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare