Using TypeScript, the Generator functions in Redux Saga do not execute nested effects in sequence when using yield put

I need to handle multiple asynchronous actions and ensure that a third action is only triggered after the first two have successfully completed. I have created three saga workers for this purpose:

export function* emailUpdateRequestSaga(action: IEmailUpdateRequest) {
  const requestURL = '/updateEmail';
  const requestData = {
    userId: action.userId,
    email: action.email
  };
  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      update,
      requestURL,
      requestData
    );

    yield put(emailUpdateSuccess({data, status}));
  } catch (err) {
    console.log('err', err);
    yield put(emailUpdateFail(err));
  }
}

The second worker handles sending an email:

export function* genericEmailRequestSaga(action: IGenericEmailRequest) {
  const requestURL = '/sendEmail';

  const requestOpt = {
    headers: {},
    body: {
      email: action.email
    }
  };

  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      post,
      requestURL,
      requestOpt
    );

    yield put(genericEmailSuccess({data, status}));
  } catch (err) {
    console.log('err', err);
    yield put(genericEmailFail(err));
  }
}

And finally, the third worker orchestrates both actions and triggers a success action only if both are successful:

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    // Call the first worker
    yield put(emailUpdateRequest(action.userId, action.email));

    // Call the second worker
    yield put(genericEmailRequest(action.email));

    // Trigger success action only if both previous actions were successful
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

This is the watcher function that connects all sagas:

export function* sagas() {
  yield takeEvery(EmailActionEnum.SEND_EMAIL_REQUEST, genericEmailRequestSaga);
  yield takeEvery(EmailActionEnum.EMAIL_UPDATE_REQUEST, emailUpdateRequestSaga);
  yield takeEvery(EmailActionEnum.EMAIL_SEND_AND_UPDATE_REQUEST, emailSendAndUpdateRequestSaga);
}

The issue I am facing is that the success action in emailSendAndUpdateRequestSaga is triggered even if the previous actions fail. How can I ensure that the third action is only triggered when both previous actions have succeeded?

Answer №1

// initial generator
yield put(emailUpdateRequest(action.userId, action.email));

// secondary generator
yield put(genericEmailRequest(action.email));

Although these lines indirectly trigger the execution of other sagas, their direct impact is limited to dispatching an action. Since dispatching actions is synchronous, this code will proceed without any delay.

If you prefer to retain the existing structure of the two sagas, you can utilize take to monitor for the corresponding actions dispatched by those sagas in order to pause your primary saga. Example:

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    yield put(emailUpdateRequest(action.userId, action.email));
    const action = yield take([
      EmailActionEnum.EMAIL_UPDATE_SUCCESS,
      EmailActionEnum.EMAIL_UPDATE_FAIL
    ]);
    if (action.type === EmailActionEnum.EMAIL_UPDATE_FAIL) {
      throw action;
    }

    yield put(genericEmailRequest(action.email));
    const action = yield take([
      EmailActionEnum.SEND_EMAIL_SUCCESS,
      EmailActionEnum.SEND_EMAIL_FAIL
    ]);

    if (action.type === EmailActionEnum.SEND_EMAIL_FAIL) {
      throw action;
    }

    // success action
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

An alternative solution could involve modifying your approach so that instead of dispatching actions, you directly call the sagas. By adjusting the sagas to throw errors when necessary, you can implement the following:

export function* emailUpdateRequestSaga(action: IEmailUpdateRequest) {
  const requestURL = '/updateEmail';
  const requestData = {
    userId: action.userId,
    email: action.email
  };
  try {
    const {data, status}: Pick<AxiosResponse, 'data' | 'status'> = yield call(
      update,
      requestURL,
      requestData
    );

    yield put(emailUpdateSuccess({data, status}));
  } catch (err) {
    yield put(emailUpdateFail(err));
    throw err; // rethrowing the error
  }
}

export function* genericEmailRequestSaga(action: IGenericEmailRequest) {
 // ... code omitted. Add a throw statement similar to emailUpdateRequestSaga
}

export function* emailSendAndUpdateRequestSaga(action: IEmailSendAndUpdateRequest) {
  try {
    // Changed to `call` instead of `put`
    yield call(emailUpdateRequestSaga, emailUpdateRequest(action.userId, action.email));

    // Changed to `call` instead of `put`
    yield call(genericEmailRequestSaga, genericEmailRequest(action.email));

    // success action
    yield put(emailSendAndUpdateSuccess(true));

  } catch (err) {
    console.log('err', err);
    yield put(emailSendAndUpdateFail(err));
  }
}

yield call(/*etc*/) will execute the specified saga until completion. The main saga will halt until the called saga finishes or throws an error.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be the reason for the Angular dropdown values not appearing?

Encountering an issue with binding data to a dropdown element, as the dropdown displays NOTHING SELECTED. <select #classProductTypeCombobox name="classProductTypeCombobox" class="form-control col-md-3" [(ngModel)]="classifica ...

What is the best way to swap out the if else statement with a Ternary operator within a JavaScript function?

Is there a way to replace the if else statement in the function using a Ternary operator in JavaScript? private getProductName(productType: string): string { let productName = 'Product not found'; this.deal.packages.find(p => p.isSele ...

Ensure all fields in an interface are nullable when using TypeScript

Is it possible to create type constraints in TypeScript that ensure all fields in an interface have a type of null? For example, if I want to write a validation function that replaces all false values with null, how can I achieve this? interface y { ...

Updating a subscribed observable does not occur when pushing or nexting a value from an observable subject

Help needed! I've created a simple example that should be working, but unfortunately it's not :( My onClick() function doesn't seem to trigger the console.log as expected. Can someone help me figure out what I'm doing wrong? @Component ...

Converting an array of objects to an array based on an interface

I'm currently facing an issue with assigning an array of objects to an interface-based array. Here is the current implementation in my item.ts interface: export interface IItem { id: number, text: string, members: any } In the item.component.ts ...

Cannon-js: Experience dynamic body bouncing on the y axis as it reacts to force applied on the x and z axes

Currently, I am working on an FPS game where the player controller applies force based on keyboard inputs to a dynamic cannon body. The angular dampening is set to 1 on the player body. The PlayerController class takes both the player class (which extends ...

Error: The FactoryMethod.render() function requires a valid React element to be returned, or null

An error has occurred: Error: FactoryMethod.render(): A valid React element (or null) must be returned. You may have returned undefined, an array, or some other invalid object. at invariant (react-dom.js:17896) Despite everything being fine during co ...

Using React and TypeScript to conditionally set props in a component

I am trying to assign a value to the component's prop when a variable is defined. Below you can find my current code. import Cropper from 'react-easy-crop' ... interface State { ... coverFile: File | null; ... } class Test extends React ...

What steps should be taken to resolve the error message "This Expression is not constructable"?

I'm trying to import a JavaScript class into TypeScript, but I keep getting the error message This expression is not constructable.. The TypeScript compiler also indicates that A does not have a constructor signature. Can anyone help me figure out how ...

RC7 is missing the necessary HTTP_PROVIDERS for the resolveAndCreate HTTP method in Angular2

During the time of RC4, I was able to create my own custom http instance using a function like this: export function createHTTP(url:string, headers?:Headers){ let injector = ReflectiveInjector.resolveAndCreate([ myHttp, {provide:'defaultUrl ...

Error: The jasmine framework is unable to locate the window object

Currently, I am testing a method that includes locking the orientation of the screen as one of its functionalities. However, when using Jasmine, I encountered an error at the following line: (<any>window).screen.orientation.lock('portrait&apos ...

Centering on request, Google Maps adjusts its view to focus on

When I select a row, I want to set the map center to the provided coordinates in Primeng. The issue is that while this.options works fine in ngOnInit, it doesn't work when called in the showCords() function. Below is my code: gmap.component.ts im ...

What is the process for importing libraries from a different local directory?

What I mean by that title is: I have some code that was generated and now I am incorporating it into my Angular application. Currently, I am installing this code as a package using npm, but it is causing issues with my deployment setup. So, I would like ...

Issues with Vercel's JavaScript Environment Variables Accessibility

I am encountering an issue trying to access environment variables on Vercel using JavaScript (TypeScript). Despite setting them under /settings/environment-variables, I receive undefined when attempting to access them with process.env.TURSO_DATABASE_URL du ...

The specified JSX element does no possess any constructors or callable signatures

The root element on the right side of my page is a simple React element that I am currently using. Can you help me troubleshoot and fix the error that is being displayed? https://i.sstatic.net/xdDyn.png ...

The Angular build is unsuccessful due to the presence of components from a separate Angular project

Whenever I execute ng build project1 --prod, the build fails and displays this error message: ERROR in : Cannot determine the module for class MyComponent in .../project2/app/my.component.ts! Add MyComponent to the NgModule to fix it.. Although the sol ...

Navigating within Redux Toolkit's actions in React js is as simple as making a call

My redux-toolkit app has a feature where I dispatch createProduct actions successfully. However, I am now facing an issue with navigating from the /products/new page to the /products/ page after the createProduct action. What is the correct way to use navi ...

Leveraging constructors for injecting dependencies in Angular is a key practice for enhancing modularity and maintainability

After reviewing the Angular Official documents and various blogs, I noticed that there are two different syntaxes for Dependency Injection (DI) when used within the constructor. Sometimes this is utilized, while other times it is not. This leads to the que ...

What is the best way to prevent jest.mock from being hoisted and only use it in a single jest unit test?

My goal is to create a mock import that will be used only in one specific jest unit test, but I am encountering some challenges. Below is the mock that I want to be restricted to just one test: jest.mock("@components/components-chat-dialog", () ...

Unexplained Reference Error in Next.js Typescript: Variable Accessed before Initialization

I am currently working on an admin website and encountered the error Block-scoped variable used before its declaration.. I will provide details using images and code. This is my first time seeking help on StackOverflow. Error Message: Block-scoped variab ...